LabBuilder.psm1

#Region './prefix.ps1' 0
<#
.EXTERNALHELP LabBuilder-help.xml
#>

#Requires -version 5.1
#Requires -RunAsAdministrator

$script:LabBuidlerModuleRoot = Split-Path `
    -Path $MyInvocation.MyCommand.Path `
    -Parent

#region LocalizedData
$culture = $PSUICulture

if ([System.String]::IsNullOrEmpty($culture))
{
    $culture = 'en-US'
}
else
{
    if (Test-Path -Path (Join-Path -Path $script:LabBuidlerModuleRoot -ChildPath $culture))
    {
        $culture = 'en-US'
    }
}

Import-LocalizedData `
    -BindingVariable LocalizedData `
    -Filename 'LabBuilder.strings.psd1' `
    -BaseDirectory $script:LabBuidlerModuleRoot `
    -UICulture $culture
#endregion

#region Types
Enum LabOStype {
    Server = 1
    Nano = 2
    Client = 3
} # Enum LabOStype

Enum LabVHDType {
    Fixed = 1
    Dynamic = 2
    Differencing = 3
} # Enum LabVHDType

Enum LabVHDFormat {
    VHD = 1
    VHDx = 2
} # Enum LabVHDFormat

Enum LabSwitchType {
    Private = 1
    Internal = 2
    External = 3
    NAT = 4
} # Enum LabSwitchType

Enum LabPartitionStyle {
    MBR = 1
    GPT = 2
} # Enum LabPartitionStyle

Enum LabFileSystem {
    FAT32 = 1
    exFAT = 2
    NTFS = 3
    ReFS = 4
} # Enum LabFileSystem

Enum LabCertificateSource {
    Guest = 1
    Host = 2
} # Enum LabCertificateSource

class LabResourceModule:System.ICloneable {
    [System.String] $Name
    [System.String] $URL
    [System.String] $Folder
    [System.String] $MinimumVersion
    [System.String] $RequiredVersion

    LabResourceModule() {}

    LabResourceModule($Name) {
        $this.Name = $Name
    } # Constructor

    [Object] Clone () {
        $New = [LabResourceModule]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabResourceModule

class LabResourceMSU:System.ICloneable {
    [System.String] $Name
    [System.String] $URL
    [System.String] $Path
    [System.String] $Filename

    LabResourceMSU() {}

    LabResourceMSU($Name) {
        $this.Name = $Name
    } # Constructor

    LabResourceMSU($Name,$URL) {
        $this.Name = $Name
        $this.URL = $URL
    } # Constructor

    [Object] Clone () {
        $New = [LabResourceMSU]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabResourceMSU

class LabResourceISO:System.ICloneable {
    [System.String] $Name
    [System.String] $URL
    [System.String] $Path

    LabResourceISO() {}

    LabResourceISO($Name) {
        $this.Name = $Name
    } # Constructor

    [Object] Clone () {
        $New = [LabResourceISO]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabResourceISO

class LabSwitchAdapter:System.ICloneable {
    [System.String] $Name
    [System.String] $MACAddress
    [Byte] $Vlan

    LabSwitchAdapter() {}

    LabSwitchAdapter($Name) {
        $this.Name = $Name
    } # Constructor

    LabSwitchAdapter($Name,$Type) {
        $this.Name = $Name
        $this.Type = $Type
    } # Constructor

    [Object] Clone () {
        $New = [LabSwitchAdapter]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabSwitchAdapter

class LabVMAdapterIPv4:System.ICloneable {
    [System.String] $Address
    [System.String] $DefaultGateway
    [Byte] $SubnetMask
    [System.String] $DNSServer

    LabVMAdapterIPv4() {}

    LabVMAdapterIPv4($Address,$SubnetMask) {
        $this.Address = $Address
        $this.SubnetMask = $SubnetMask
    } # Constructor

    [Object] Clone () {
        $New = [LabVMAdapterIPv4]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVMAdapterIPv4

class LabVMAdapterIPv6:System.ICloneable {
    [System.String] $Address
    [System.String] $DefaultGateway
    [Byte] $SubnetMask
    [System.String] $DNSServer

    LabVMAdapterIPv6() {}

    LabVMAdapterIPv6($Address,$SubnetMask) {
        $this.Address = $Address
        $this.SubnetMask = $SubnetMask
    } # Constructor

    [Object] Clone () {
        $New = [LabVMAdapterIPv6]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVMAdapterIPv6

class LabVMAdapter:System.ICloneable {
    [System.String] $Name
    [System.String] $SwitchName
    [System.String] $MACAddress
    [System.Boolean] $MACAddressSpoofing
    [Byte] $Vlan
    [LabVMAdapterIPv4] $IPv4
    [LabVMAdapterIPv6] $IPv6

    LabVMAdapter() {}

    LabVMAdapter($Name) {
        $this.Name = $Name
    } # Constructor

    [Object] Clone () {
        $New = [LabVMAdapter]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVMAdapter

class LabDataVHD:System.ICloneable {
    [System.String] $VHD
    [LabVHDType] $VHDType
    [Uint64] $Size
    [System.String] $SourceVHD
    [System.String] $ParentVHD
    [System.Boolean] $MoveSourceVHD
    [System.String] $CopyFolders
    [LabFileSystem] $FileSystem
    [LabPartitionStyle] $PartitionStyle
    [System.String] $FileSystemLabel
    [System.Boolean] $Shared = $false
    [System.Boolean] $SupportPR = $false

    LabDataVHD() {}

    LabDataVHD($VHD) {
        $this.VHD = $VHD
    } # Constructor

    [Object] Clone () {
        $New = [LabDataVHD]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabDataVHD

class LabDVDDrive:System.ICloneable {
    [System.String] $ISO
    [System.String] $Path

    LabDVDDrive() {}

    LabDVDDrive($ISO) {
        $this.ISO = $ISO
    } # Constructor

    [Object] Clone () {
        $New = [LabDVDDrive]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabDVDDrive

class LabVMTemplateVHD:System.ICloneable {
    [System.String] $Name
    [System.String] $ISOPath
    [System.String] $VHDPath
    [LabOStype] $OSType = [LabOStype]::Server
    [System.String] $Edition
    [Byte] $Generation = 2
    [LabVHDFormat] $VHDFormat = [LabVHDFormat]::VHDx
    [LabVHDType] $VHDType = [LabVHDType]::Dynamic
    [Uint64] $VHDSize = 0
    [System.String[]] $Packages
    [System.String[]] $Features

    LabVMTemplateVHD() {}

    LabVMTemplateVHD($Name) {
        $this.Name = $Name
    } # Constructor

    [Object] Clone () {
        $New = [LabVMTemplateVHD]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVMTemplateVHD

class LabVMTemplate:System.ICloneable {
    [System.String] $Name
    [System.String] $VHD
    [System.String] $SourceVHD
    [System.String] $ParentVHD
    [System.String] $TemplateVHD
    [Uint64] $MemoryStartupBytes = 1GB
    [System.Boolean] $DynamicMemoryEnabled = $true
    [System.Boolean] $ExposeVirtualizationExtensions = $false
    [Byte] $ProcessorCount = 1
    [System.String] $AdministratorPassword
    [System.String] $ProductKey
    [System.String] $Timezone="Pacific Standard Time"
    [LabOStype] $OSType = [LabOStype]::Server
    [System.String[]] $IntegrationServices = @('Guest Service Interface','Heartbeat','Key-Value Pair Exchange','Shutdown','Time Synchronization','VSS')
    [System.String[]] $Packages
    [ValidateRange(1,2)][Byte] $Generation = 2
    [ValidateSet("5.0","6.2","7.0","7.1","8.0","254.0","255.0")][System.String] $Version = '8.0'

    LabVMTemplate() {}

    LabVMTemplate($Name) {
        $this.Name = $Name
    } # Constructor

    [Object] Clone () {
        $New = [LabVMTemplate]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVMTemplate

class LabSwitch:System.ICloneable {
    [System.String] $Name
    [LabSwitchType] $Type
    [Byte] $VLAN
    [System.String] $BindingAdapterName
    [System.String] $BindingAdapterMac
    [System.String] $NatSubnet
    [System.String] $NatGatewayAddress
    [LabSwitchAdapter[]] $Adapters

    LabSwitch() {}

    LabSwitch($Name) {
        $this.Name = $Name
    } # Constructor

    LabSwitch($Name,$Type) {
        $this.Name = $Name
        $this.Type = $Type
    } # Constructor

    [Object] Clone () {
        $New = [LabSwitch]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabSwitch

class LabDSC:System.ICloneable {
    [System.String] $ConfigName
    [System.String] $ConfigFile
    [System.String] $Parameters
    [System.Boolean] $Logging = $false

    LabDSC() {}

    LabDSC($ConfigName) {
        $this.ConfigName = $ConfigName
    } # Constructor

    LabDSC($ConfigName,$ConfigFile) {
        $this.ConfigName = $ConfigName
        $this.ConfigFile = $ConfigFile
    } # Constructor

    [Object] Clone () {
        $New = [LabDSC]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabDSC

class LabVM:System.ICloneable {
    [System.String] $Name
    [System.String] $Template
    [System.String] $ComputerName
    [Byte] $ProcessorCount
    [Uint64] $MemoryStartupBytes = 1GB
    [System.Boolean] $DynamicMemoryEnabled = $true
    [System.Boolean] $ExposeVirtualizationExtensions = $false
    [System.String] $ParentVHD
    [System.Boolean] $UseDifferencingDisk = $true
    [System.String] $AdministratorPassword
    [System.String] $ProductKey
    [System.String] $Timezone="Pacific Standard Time"
    [LabOStype] $OSType = [LabOStype]::Server
    [System.String] $UnattendFile
    [System.String] $SetupComplete
    [System.String[]] $Packages
    [ValidateRange(1,2)][Byte] $Generation = 2
    [ValidateSet("5.0","6.2","7.0","7.1","8.0","254.0","255.0")][System.String] $Version = '8.0'
    [System.Int32] $BootOrder
    [System.String[]] $IntegrationServices = @('Guest Service Interface','Heartbeat','Key-Value Pair Exchange','Shutdown','Time Synchronization','VSS')
    [LabVMAdapter[]] $Adapters
    [LabDataVHD[]] $DataVHDs
    [LabDVDDrive[]] $DVDDrives
    [LabDSC] $DSC
    [System.String] $VMRootPath
    [System.String] $LabBuilderFilesPath
    [LabCertificateSource] $CertificateSource = [LabCertificateSource]::Guest
    [System.String] $NanoODJPath

    LabVM() {}

    LabVM($Name) {
        $this.Name = $Name
    } # Constructor

    LabVM($Name,$ComputerName) {
        $this.Name = $Name
        $this.ComputerName = $ComputerName
    } # Constructor

    [Object] Clone () {
        $New = [LabVM]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabVM

class LabDSCModule:System.ICloneable {
    [System.String] $ModuleName
    [Version] $ModuleVersion
    [Version] $MinimumVersion

    LabDSCModule() {}

    LabDSCModule($ModuleName) {
        $this.ModuleName = $ModuleName
    } # Constructor

    LabDSCModule($ModuleName,$ModuleVersion) {
        $this.ModuleName = $ModuleName
        $this.ModuleVersion = [Version] $ModuleVersion
    } # Constructor

    [Object] Clone () {
        $New = [LabDSCModule]::New()
        foreach ($Property in ($this | Get-Member -MemberType Property))
        {
            $New.$($Property.Name) = $this.$($Property.Name)
        } # foreach
        return $New
    } # Clone
} # class LabDSCModule
#endregion

#region ModuleVariables
[System.String] $script:WorkingFolder = $ENV:Temp

# Supporting files
[System.String] $script:SupportConvertWindowsImagePath = Join-Path `
    -Path $PSScriptRoot `
    -ChildPath 'support\Convert-WindowsImage.ps1'
[System.String] $script:SupportGertGenPath = Join-Path `
    -Path $PSScriptRoot `
    -ChildPath 'support\New-SelfSignedCertificateEx.ps1'

# DSC Library
[System.String] $script:DSCLibraryPath = Join-Path `
    -Path $PSScriptRoot `
    -ChildPath 'dsclibrary'

# Virtual Networking Parameters
[System.Int32] $script:DefaultManagementVLan = 99

# Self-signed Certificate Parameters
[System.Int32] $script:SelfSignedCertKeyLength = 2048
# Warning - using KSP causes the Private Key to not be accessible to PS.
[System.String] $script:SelfSignedCertProviderName = 'Microsoft Enhanced Cryptographic Provider v1.0' # 'Microsoft Software Key Storage Provider'
[System.String] $script:SelfSignedCertAlgorithmName = 'RSA' # 'ECDH_P256' Or 'ECDH_P384' Or 'ECDH_P521'
[System.String] $script:SelfSignedCertSignatureAlgorithm = 'SHA256' # 'SHA1'
[System.String] $script:DSCEncryptionCert = 'DSCEncryption.cer'
[System.String] $script:DSCEncryptionPfxCert = 'DSCEncryption.pfx'
[System.String] $script:DSCCertificateFriendlyName = 'DSC Credential Encryption'
[System.String] $script:DSCCertificatePassword = 'E3jdNkd903mDn43NEk2nbDENjw'
[System.Int32] $script:RetryConnectSeconds = 5
[System.Int32] $script:RetryHeartbeatSeconds = 1
[System.Int32] $script:StartupTimeout = 90

# System Info
[System.Int32] $script:currentBuild = (Get-ItemProperty `
    -Path 'hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuild

# XML Stuff
[System.String] $script:ConfigurationXMLSchema = Join-Path `
    -Path $PSScriptRoot `
    -ChildPath 'schema\labbuilderconfig-schema.xsd'
[System.String] $script:ConfigurationXMLTemplate = Join-Path `
    -Path $PSScriptRoot `
    -ChildPath 'template\labbuilderconfig-template.xml'

# Nano Stuff
[System.String] $script:NanoPackageCulture = 'en-us'
#EndRegion './prefix.ps1' 540
#Region './Private/Assert-LabValidConfigurationXMLSchema.ps1' 0
<#
    .SYNOPSIS
        Validates the provided configuration XML against the Schema.

    .DESCRIPTION
        This function will ensure that the provided Configration XML
        is compatible with the LabBuilderConfig.xsd Schema file.

    .PARAMETER ConfigPath
        Contains the path to the Configuration XML file.

    .EXAMPLE
        Assert-LabValidConfigurationXMLSchema -ConfigPath c:\mylab\config.xml
        Validates the XML configuration and downloads any resources required by it.

    .OUTPUTS
        None. If the XML is invalid an exception will be thrown.
#>

function Assert-LabValidConfigurationXMLSchema
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ConfigPath
    )

    # Define these variables so they are accesible inside the event handler.
    $script:XMLErrorCount = 0
    $script:XMLFirstError = ''
    $script:XMLPath = $ConfigPath
    $script:ConfigurationXMLValidationMessage = $LocalizedData.ConfigurationXMLValidationMessage

    # Perform the XSD Validation
    $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings
    $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema
    $null = $readerSettings.Schemas.Add("labbuilderconfig", $script:ConfigurationXMLSchema)
    $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessInlineSchema -bor [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation
    $readerSettings.add_ValidationEventHandler(
        {
            # Triggered each time an error is found in the XML file
            if ([System.String]::IsNullOrWhitespace($script:XMLFirstError))
            {
                $script:XMLFirstError = $_.Message
            } # if
            Write-LabMessage -Message ($script:ConfigurationXMLValidationMessage `
                    -f $script:XMLPath, $_.Message)
            $script:XMLErrorCount++
        })

    $reader = [System.Xml.XmlReader]::Create([System.String] $ConfigPath, $readerSettings)

    try
    {
        while ($reader.Read())
        {
        } # while
    } # try
    catch
    {
        # XML is NOT valid
        $exceptionParameters = @{
            errorId       = 'ConfigurationXMLValidationError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.ConfigurationXMLValidationError `
                    -f $ConfigPath, $_.Exception.Message)
        }
        New-LabException @exceptionParameters
    } # catch
    finally
    {
        $null = $reader.Close()
    } # finally

    # Verify the results of the XSD validation
    if ($script:XMLErrorCount -gt 0)
    {
        # XML is NOT valid
        $exceptionParameters = @{
            errorId       = 'ConfigurationXMLValidationError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.ConfigurationXMLValidationError -f $ConfigPath, $script:XMLFirstError)
        }
        New-LabException @exceptionParameters
    } # if
}
#EndRegion './Private/Assert-LabValidConfigurationXMLSchema.ps1' 89
#Region './Private/Assert-LabValidIpAddress.ps1' 0
<#
    .SYNOPSIS
        Validates the IP Address.

    .PARAMETER IpAddress
        Contains the IP Address to validate.

    .EXAMPLE
        Assert-LabValidIpAddress -IpAddress '192.168.123.44'
        Does not throw an exception and returns '192.168.123.44'.

    .EXAMPLE
        Assert-LabValidIpAddress -IpAddress '192.168.123.4432'
        Throws an exception.

    .OUTPUTS
        The IP address if valid.
#>

function Assert-LabValidIpAddress
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $IpAddress
    )

    $ip = [System.Net.IPAddress]::Any
    if (-not [System.Net.IPAddress]::TryParse($IpAddress, [ref] $ip))
    {
        $exceptionParameters = @{
            errorId       = 'IPAddressError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.IPAddressError -f $IpAddress)
        }
        New-LabException @exceptionParameters
    }
    return $ip
}
#EndRegion './Private/Assert-LabValidIpAddress.ps1' 42
#Region './Private/ConvertTo-LabAbsolutePath.ps1' 0
<#
    .SYNOPSIS
        Convert a path to be an absolute by adding it to the base path.
        If the path is already absolute then it is just returned as is.

    .PARAMETER Path
        The path to convert to an absolute path.

    .PARAMETER BasePath
        The full path to the lab.

    .OUTPUTS
        The path converted to an absolute path.
#>

function ConvertTo-LabAbsolutePath
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [Parameter(Mandatory = $true)]
        [System.String]
        $BasePath
    )

    if (-not [System.IO.Path]::IsPathRooted($Path))
    {
        $Path = Join-Path `
            -Path $BasePath `
            -ChildPath $Path
    } # if

    return $Path
}
#EndRegion './Private/ConvertTo-LabAbsolutePath.ps1' 38
#Region './Private/Copy-LabOdjFile.ps1' 0
<#
    .SYNOPSIS
        Uploads Precreated ODJ files to Nano systems or others as required.

    .DESCRIPTION
        This function will perform the following tasks:
            1. Connect to the VM via remoting.
            2. Upload the ODJ file to c:\windows\setup\ODJFiles folder of the VM.
        If the ODJ file does not exist in the LabFiles folder for the VM then the
        copy will not be performed.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .PARAMETER Timeout
        The maximum amount of time that this function can take to perform the copy.
        If the timeout is reached before the process is complete an error will be thrown.
        The timeout defaults to 300 seconds.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Copy-LabOdjFile -Lab $Lab -VM $VMs[0]

    .OUTPUTS
        None.
#>

function Copy-LabOdjFile
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [System.Int32]
        $Timeout = 300
    )

    $startTime = Get-Date
    $session = $null
    $complete = $false
    $odjCopyComplete = $false
    $odjFilename = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath "$($VM.ComputerName).txt"

    # If ODJ file does not exist then return
    if (-not (Test-Path -Path $odjFilename))
    {
        return
    } # if

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    while ((-not $complete) `
        -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
    {
        # Connect to the VM
        $session = Connect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        # Failed to connnect to the VM
        if (-not $session)
        {
            $exceptionParameters = @{
                errorId = 'ODJCopyError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.ODJCopyError `
                    -f $VM.Name,$odjFilename)
            }
            New-LabException @exceptionParameters
            return
        } # if

        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and (-not $odjCopyComplete))
        {
            $CopyParameters = @{
                Destination = 'C:\Windows\Setup\ODJFiles\'
                ToSession = $session
                Force = $true
                ErrorAction = 'Stop'
            }

            # Connection has been made OK, upload the ODJ files
            while ((-not $odjCopyComplete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                try
                {
                    Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMMessage `
                        -f $VM.Name,'ODJ')

                     Copy-Item `
                        @CopyParameters `
                        -Path (Join-Path `
                            -Path $vmLabBuilderFiles `
                            -ChildPath "$($VM.ComputerName).txt") `
                        -Verbose
                    $odjCopyComplete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMFailedMessage `
                        -f $VM.Name,'ODJ',$script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        # If the copy didn't complete and we're out of time throw an exception
        if ((-not $odjCopyComplete) `
            -and (((Get-Date) - $startTime).TotalSeconds) -ge $TimeOut)
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $exceptionParameters = @{
                errorId = 'ODJCopyError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.ODJCopyError `
                    -f $VM.Name,$odjFilename)
            }
            New-LabException @exceptionParameters
        } # if


        # Disconnect from the VM
        Disconnect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        $complete = $true
    } # while
}
#EndRegion './Private/Copy-LabOdjFile.ps1' 150
#Region './Private/Enable-LabWSMan.ps1' 0
<#
    .SYNOPSIS
        Ensures the WS-Man is configured on this system.

    .DESCRIPTION
        If WS-Man is not enabled on this system it will be enabled.
        This is required to communicate with the managed Lab Virtual Machines.

    .EXAMPLE
        Enable-LabWSMan
        Enables WS-Man on this machine.

    .OUTPUTS
        None
#>

function Enable-LabWSMan
{
    [CmdLetBinding()]
    param
    (
        [Parameter()]
        [Switch]
        $Force
    )

    if (-not (Get-PSPRovider -PSProvider WSMan -ErrorAction SilentlyContinue))
    {
        Write-LabMessage -Message ($LocalizedData.EnablingWSManMessage)

        try
        {
            Start-Service -Name WinRm -ErrorAction Stop
        }
        catch
        {
            $null = Enable-PSRemoting `
                @PSBoundParameters `
                -SkipNetworkProfileCheck `
                -ErrorAction Stop
        }

        # Check WS-Man was enabled
        if (-not (Get-PSProvider -PSProvider WSMan -ErrorAction SilentlyContinue))
        {
            $exceptionParameters = @{
                errorId       = 'WSManNotEnabledError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.WSManNotEnabledError)
            }
            New-LabException @exceptionParameters
        } # if
    } # if

    # Make sure the WinRM service is running
    if ((Get-Service -Name WinRM).Status -ne 'Running')
    {
        try
        {
            Start-Service -Name WinRm -ErrorAction Stop
        }
        catch
        {
            $exceptionParameters = @{
                errorId       = 'WinRMServiceFailedToStartError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.WinRMServiceFailedToStartError)
            }
            New-LabException @exceptionParameters
        }
    }
}
#EndRegion './Private/Enable-LabWSMan.ps1' 72
#Region './Private/Get-LabBuilderModulePath.ps1' 0
<#
    .SYNOPSIS
        Returns the path of the currently loaded LabBuilder module.

    .OUTPUTS
        The path to the currently loaded LabBuilder module.
#>

function Get-LabBuilderModulePath
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param ()

    return $script:LabBuidlerModuleRoot
}
#EndRegion './Private/Get-LabBuilderModulePath.ps1' 16
#Region './Private/Get-LabCertificatePsFileContent.ps1' 0
<#
    .SYNOPSIS
        Assemble the the PowerShell commands required to create a self-signed certificate.

    .DESCRIPTION
        This function creates the content that can be written into a PS1 file to create a self-signed
        certificate.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        $CertificatePS = Get-LabCertificatePsFileContent -Lab $Lab -VM $VMs[0]
        Return the Create Self-Signed Certificate script for the first VM in the
        Lab c:\mylab\config.xml for DSC configuration.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .PARAMETER CertificateSource
        A CertificateSource to use instead of the one contained in the VM.

    .OUTPUTS
        A string containing the Create Self-Signed Certificate PowerShell code.
#>

function Get-LabCertificatePsFileContent
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter()]
        [LabCertificateSource]
        $CertificateSource
    )

    # If a CertificateSource is not provided get it from the VM.
    if (-not $CertificateSource)
    {
        $CertificateSource = $VM.CertificateSource
    } # if

    if ($CertificateSource -eq [LabCertificateSource]::Guest)
    {
        $createCertificatePs = @"
`$CertificateFriendlyName = '$($script:DSCCertificateFriendlyName)'
`$Cert = Get-ChildItem -Path cert:\LocalMachine\My ``
    | Where-Object { `$_.FriendlyName -eq `$CertificateFriendlyName } ``
    | Select-Object -First 1
if (-not `$Cert)
{
    . `"`$(`$ENV:SystemRoot)\Setup\Scripts\New-SelfSignedCertificateEx.ps1`"
    New-SelfsignedCertificateEx ``
        -Subject 'CN=$($VM.ComputerName)' ``
        -EKU '1.3.6.1.4.1.311.80.1','1.3.6.1.5.5.7.3.1','1.3.6.1.5.5.7.3.2' ``
        -KeyUsage 'DigitalSignature, KeyEncipherment, DataEncipherment' ``
        -SAN '$($VM.ComputerName)' ``
        -FriendlyName `$CertificateFriendlyName ``
        -Exportable ``
        -StoreLocation 'LocalMachine' ``
        -StoreName 'My' ``
        -KeyLength $($script:SelfSignedCertKeyLength) ``
        -ProviderName '$($script:SelfSignedCertProviderName)' ``
        -AlgorithmName $($script:SelfSignedCertAlgorithmName) ``
        -SignatureAlgorithm $($script:SelfSignedCertSignatureAlgorithm)
    # There is a slight delay before new cert shows up in Cert:
    # So wait for it to show.
    While (-not `$Cert)
    {
        `$Cert = Get-ChildItem -Path cert:\LocalMachine\My ``
            | Where-Object { `$_.FriendlyName -eq `$CertificateFriendlyName }
    }
}
Export-Certificate ``
    -Type CERT ``
    -Cert `$Cert ``
    -FilePath `"`$(`$ENV:SystemRoot)\$script:DSCEncryptionCert`"
Add-Content ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
    -Value 'Encryption Certificate Imported from CER ...' ``
    -Encoding Ascii
"@

    }
    else
    {
        [System.String] $createCertificatePs = @"
if (Test-Path -Path `"`$(`$ENV:SystemRoot)\$script:DSCEncryptionPfxCert`")
{
    `$CertificatePassword = ConvertTo-SecureString ``
        -String '$script:DSCCertificatePassword' ``
        -Force ``
        -AsPlainText
    Add-Content ``
        -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
        -Value 'Importing Encryption Certificate from PFX ...' ``
        -Encoding Ascii
    Import-PfxCertificate ``
        -Password '$script:DSCCertificatePassword' ``
        -FilePath `"`$(`$ENV:SystemRoot)\$script:DSCEncryptionPfxCert`" ``
        -CertStoreLocation cert:\localMachine\root
    Add-Content ``
        -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
        -Value 'Encryption Certificate from PFX Imported...' ``
        -Encoding Ascii
}
"@

    } # if

    return $createCertificatePs
}
#EndRegion './Private/Get-LabCertificatePsFileContent.ps1' 120
#Region './Private/Get-LabDSCNetworkingConfig.ps1' 0
<#
    .SYNOPSIS
        Assemble the content of the Networking DSC config file.

    .DESCRIPTION
        This function creates the content that will be written to the Networking DSC Config file
        from the networking details stored in the VM object.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        $NetworkingDsc = Get-LabDSCNetworkingConfig -Lab $Lab -VM $VMs[0]
        Return the Networking DSC for the first VM in the Lab c:\mylab\config.xml for DSC configuration.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .OUTPUTS
        A string containing the DSC Networking config.
#>

function Get-LabDSCNetworkingConfig
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    $NetworkingDscVersion = (`
            Get-Module -Name NetworkingDsc -ListAvailable `
            | Sort-Object version -Descending `
            | Select-Object -First 1 `
    ).Version.ToString()

    $dscNetworkingConfig = @"
Configuration Networking {
    Import-DscResource -ModuleName NetworkingDsc -ModuleVersion $NetworkingDscVersion

"@

    $adapterCount = 0

    foreach ($Adapter in $VM.Adapters)
    {
        $adapterCount++

        if ($adapter.IPv4)
        {
            if (-not [System.String]::IsNullOrWhitespace($adapter.IPv4.Address))
            {
                $dscNetworkingConfig += @"
    IPAddress IPv4_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv4'
        IPAddress = '$($adapter.IPv4.Address.Replace(',',"','"))/$($adapter.IPv4.SubnetMask)'
    }

"@

                if (-not [System.String]::IsNullOrWhitespace($adapter.IPv4.DefaultGateway))
                {
                    $dscNetworkingConfig += @"
    DefaultGatewayAddress IPv4G_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv4'
        Address = '$($adapter.IPv4.DefaultGateway)'
    }

"@

                }
                else
                {
                    $dscNetworkingConfig += @"
    DefaultGatewayAddress IPv4G_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv4'
    }

"@

                } # if
            }
            else
            {
                $dscNetworkingConfig += @"
    NetIPInterface IPv4DHCP_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv4'
        Dhcp = 'Enabled'
    }

"@


            } # if

            if (-not [System.String]::IsNullOrWhitespace($adapter.IPv4.DNSServer))
            {
                $dscNetworkingConfig += @"
    DnsServerAddress IPv4D_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv4'
        Address = '$($adapter.IPv4.DNSServer.Replace(',',"','"))'
    }

"@

            } # if
        } # if

        if ($adapter.IPv6)
        {
            if (-not [System.String]::IsNullOrWhitespace($adapter.IPv6.Address))
            {
                $dscNetworkingConfig += @"
    IPAddress IPv6_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv6'
        IPAddress = '$($adapter.IPv6.Address.Replace(',',"','"))/$($adapter.IPv6.SubnetMask)'
    }

"@

                if (-not [System.String]::IsNullOrWhitespace($adapter.IPv6.DefaultGateway))
                {
                    $dscNetworkingConfig += @"
    DefaultGatewayAddress IPv6G_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv6'
        Address = '$($adapter.IPv6.DefaultGateway)'
    }

"@

                }
                else
                {
                    $dscNetworkingConfig += @"
    DefaultGatewayAddress IPv6G_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv6'
    }

"@

                } # if
            }
            else
            {
                $dscNetworkingConfig += @"
    NetIPInterface IPv6DHCP_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv6'
        Dhcp = 'Enabled'
    }

"@


            } # if

            if (-not [System.String]::IsNullOrWhitespace($adapter.IPv6.DNSServer))
            {
                $dscNetworkingConfig += @"
    DnsServerAddress IPv6D_$adapterCount {
        InterfaceAlias = '$($adapter.Name)'
        AddressFamily = 'IPv6'
        Address = '$($adapter.IPv6.DNSServer.Replace(',',"','"))'
    }

"@

            } # if
        } # if
    } # endfor

    $dscNetworkingConfig += @"
}
"@


    return $dscNetworkingConfig
} # Get-LabDSCNetworkingConfig

[DSCLocalConfigurationManager()]
Configuration ConfigLCM {
    param (
        [System.String] $ComputerName,
        [System.String] $Thumbprint
    )
    Node $ComputerName {
        Settings
        {
            RefreshMode                    = 'Push'
            ConfigurationMode              = 'ApplyAndAutoCorrect'
            CertificateId                  = $Thumbprint
            ConfigurationModeFrequencyMins = 15
            RefreshFrequencyMins           = 30
            RebootNodeIfNeeded             = $true
            ActionAfterReboot              = 'ContinueConfiguration'
        }
    }
}
#EndRegion './Private/Get-LabDSCNetworkingConfig.ps1' 202
#Region './Private/Get-LabIntegrationServiceName.ps1' 0
<#
    .SYNOPSIS
        Get list of Integration Service names (localized)

    .DESCRIPTION
        This cmdlet will get the list of Integration services available on a Hyper-V host.
        The list of Integration Services will contain the localized names.

    .EXAMPLE
        Get-LabIntegrationServiceName

    .OUTPUTS
        An array of localized Integration Serivce names.
#>

function Get-LabIntegrationServiceName
{
    [CmdLetBinding()]
    param
    (
    )

    $captions = @()
    $classes = @(
        'Msvm_VssComponentSettingData'
        'Msvm_ShutdownComponentSettingData'
        'Msvm_TimeSyncComponentSettingData'
        'Msvm_HeartbeatComponentSettingData'
        'Msvm_GuestServiceInterfaceComponentSettingData'
        'Msvm_KvpExchangeComponentSettingData'
    )

    <#
        This Integration Service is registered in CIM but is not exposed in Hyper-V:
        'Msvm_RdvComponentSettingData'
    #>


    foreach ($class in $classes)
    {
        $captions += (Get-CimInstance `
            -Class $class `
            -Namespace Root\Virtualization\V2 `
            -Property Caption | Select-Object -First 1).Caption
    } # foreach

    return $captions
}
#EndRegion './Private/Get-LabIntegrationServiceName.ps1' 47
#Region './Private/Get-LabManagementSwitchName.ps1' 0
<#
    .SYNOPSIS
        Returns the name of the Management Switch to use for this lab.

    .DESCRIPTION
        Each lab has a unique private management switch created for it.
        All Virtual Machines in the Lab are connected to the switch.
        This function returns the name of this swtich for the provided
        lab configuration.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $ManagementSwitch = Get-LabManagementSwitchName -Lab $Lab
        Returns the Management Switch for the Lab c:\mylab\config.xml.

    .OUTPUTS
        A management switch name.
#>

function Get-LabManagementSwitchName
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab
    )

    $LabId = $Lab.labbuilderconfig.settings.labid

    if (-not $LabId)
    {
        $LabId = $Lab.labbuilderconfig.name
    } # if

    $managementSwitchName = ('{0}Lab Management' -f $LabId)

    return $managementSwitchName
}
#EndRegion './Private/Get-LabManagementSwitchName.ps1' 43
#Region './Private/Get-LabModulesInDSCConfig.ps1' 0
<#
    .SYNOPSIS
        Get a list of all Resources imported in a DSC Config

    .DESCRIPTION
        Uses RegEx to pull a list of Resources that are imported in a DSC Configuration using the
        Import-DSCResource cmdlet.

        If The -ModuleVersion parameter is included then the ModuleVersion property in the returned
        LabDSCModule object will be set, otherwise it will be null.

    .PARAMETER DscConfigFile
        Contains the path to the DSC Config file to extract resource module names from.

    .PARAMETER DscConfigContent
        Contains the content of the DSC Config to extract resource module names from.

    .EXAMPLE
        Get-LabModulesInDSCConfig -DscConfigFile c:\mydsc\Server01.ps1
        Return the DSC Resource module list from file c:\mydsc\server01.ps1

    .EXAMPLE
        Get-LabModulesInDSCConfig -DscConfigContent $DSCConfig
        Return the DSC Resource module list from the DSC Config in $DSCConfig.

    .OUTPUTS
        An array of LabDSCModule objects containing the DSC Resource modules required by this DSC
        configuration file.
#>

function Get-LabModulesInDSCConfig
{
    [CmdLetBinding(DefaultParameterSetName = "Content")]
    [OutputType([Object[]])]
    param
    (
        [parameter(
            Position = 1,
            ParameterSetName = "Content",
            Mandatory = $true)]
        [System.String]
        $DscConfigContent,

        [parameter(
            Position = 2,
            ParameterSetName = "File",
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $DscConfigFile
    )

    [LabDSCModule[]] $modules = $null

    if ($PSCmdlet.ParameterSetName -eq 'File')
    {
        $DscConfigContent = Get-Content -Path $DscConfigFile -Raw
    } # if

    $regex = "[ \t]*?Import\-DscResource[ \t]+(?:\-ModuleName[ \t])?'?`"?([A-Za-z0-9._-]+)`"?'?(([ \t]+-ModuleVersion)?[ \t]+'?`"?([0-9.]+)`"?`?)?[ \t]*?[\r\n]+?"
    $moduleMatches = [regex]::matches($DscConfigContent, $regex, 'IgnoreCase')

    foreach ($moduleMatch in $moduleMatches)
    {
        $moduleName = $moduleMatch.Groups[1].Value
        $moduleVersion = $moduleMatch.Groups[4].Value
        # Make sure this module isn't already in the list

        if ($moduleName -notin $Modules.ModuleName)
        {
            $module = [LabDSCModule]::New($moduleName)

            if (-not [System.String]::IsNullOrWhitespace($moduleVersion))
            {
                $module.moduleVersion = [Version] $moduleVersion
            } # if

            $modules += @( $module )
        } # if
    } # foreach

    return $modules
}
#EndRegion './Private/Get-LabModulesInDSCConfig.ps1' 83
#Region './Private/Get-LabNextIpAddress.ps1' 0
<#
    .SYNOPSIS
        Increases the IP Address.

    .PARAMETER IpAddress
        Contains the IP Address to increase.

    .PARAMETER Step
        Contains the number of steps to increase the IP address by.

    .EXAMPLE
        Get-LabNextIpAddress -IpAddress '192.168.123.44' -Step 2
        Returns the IP Address '192.168.123.44'

    .EXAMPLE
        Get-LabNextIpAddress -IpAddress 'fe80::15b4:b934:5d23:1a2f' -Step 2
        Returns the IP Address 'fe80::15b4:b934:5d23:1a31'

    .OUTPUTS
        The increased IP Address.
#>

function Get-LabNextIpAddress
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $IpAddress,

        [Parameter()]
        [System.Byte]
        $Step = 1
    )

    # Check the IP Address is valid
    $ip = Assert-LabValidIpAddress -IpAddress $IpAddress

    # This code will increase the next IP address by the step amount.
    # It uses the IP Address byte array to do this.
    $bytes = $ip.GetAddressBytes()
    $position = $bytes.Length - 1

    while ($Step -gt 0)
    {
        if ($bytes[$position] + $Step -gt 255)
        {
            $bytes[$position] = $bytes[$position] + $Step - 256
            $Step = $Step - $bytes[$position]
            $position--
        }
        else
        {
            $bytes[$position] = $bytes[$position] + $Step
            $Step = 0
        } # if
    } # while

    return [System.Net.IPAddress]::new($bytes).IPAddressToString
}
#EndRegion './Private/Get-LabNextIpAddress.ps1' 62
#Region './Private/Get-LabNextMacAddress.ps1' 0
<#
    .SYNOPSIS
        Increases the MAC Address.

    .PARAMETER MACAddress
        Contains the MAC Address to increase.

    .PARAMETER Step
        Contains the number of steps to increase the MAC address by.

    .EXAMPLE
        Get-NextMacAddress -MacAddress '00155D0106ED' -Step 2
        Returns the MAC Address '00155D0106EF'

    .OUTPUTS
        The increased MAC Address.
#>

function Get-NextMacAddress
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $MacAddress,

        [System.Byte]
        $Step = 1
    )

    return [System.String]::Format("{0:X}", [Convert]::ToUInt64($MACAddress, 16) + $Step).PadLeft(12, '0')
}
#EndRegion './Private/Get-LabNextMacAddress.ps1' 34
#Region './Private/Get-LabUnattendFileContent.ps1' 0
<#
    .SYNOPSIS
        Assembles the content of a Unattend XML file that should be used to initialize
        Windows on the specified VM.

    .DESCRIPTION
        This function will return the content of a standard Windows Unattend XML file
        that can be written to an VHD containing a copy of Windows that is still in
        OOBE mode.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Get-LabUnattendFileContent -Lab $Lab -VM $VMs[0]
        Returns the content of the Unattend File for the first VM in the Lab c:\mylab\config.xml.

    .OUTPUTS
        The content of the Unattend File for the VM.
#>

function Get-LabUnattendFileContent
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    if ($VM.UnattendFile)
    {
        $unattendContent = Get-Content -Path $VM.UnattendFile
    }
    else
    {
        $domainName = $Lab.labbuilderconfig.settings.domainname
        $email = $Lab.labbuilderconfig.settings.email
        $unattendContent = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="offlineServicing">
        <component name="Microsoft-Windows-LUA-Settings" 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">
            <EnableLUA>false</EnableLUA>
        </component>
    </settings>
    <settings pass="generalize">
        <component name="Microsoft-Windows-Security-SPP" 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">
            <SkipRearm>1</SkipRearm>
        </component>
    </settings>
    <settings pass="specialize">
        <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>0409:00000409</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UILanguageFallback>en-US</UILanguageFallback>
            <UserLocale>en-US</UserLocale>
        </component>
        <component name="Microsoft-Windows-Security-SPP-UX" 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">
            <SkipAutoActivation>true</SkipAutoActivation>
        </component>
        <component name="Microsoft-Windows-SQMApi" 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">
            <CEIPEnabled>0</CEIPEnabled>
        </component>
        <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>$($VM.ComputerName)</ComputerName>
        </component>

"@



        if ($VM.OSType -eq [LabOSType]::Client)
        {
            $unattendContent += @"
            <component name="Microsoft-Windows-Deployment" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <RunSynchronous>
                    <RunSynchronousCommand wcm:action="add">
                        <Order>1</Order>
                        <Path>net user administrator /active:yes</Path>
                    </RunSynchronousCommand>
                    <RunSynchronousCommand wcm:action="add">
                        <Order>2</Order>
                        <Path>powershell.exe -Command "Enable-PSRemoting -SkipNetworkProfileCheck -Force"</Path>
                    </RunSynchronousCommand>
                </RunSynchronous>
            </component>

"@

        } # If

        $unattendContent += @"
    </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">
"@

        if ($VM.OSType -eq [LabOSType]::Client)
        {
            $unattendContent += @"
            <AutoLogon>
                <Password>
                    <Value>$($VM.AdministratorPassword)</Value>
                    <PlainText>true</PlainText>
                </Password>
                <Username>Administrator</Username>
                <Enabled>true</Enabled>
                <LogonCount>2</LogonCount>
            </AutoLogon>
            <FirstLogonCommands>
                <SynchronousCommand wcm:action="add">
                    <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
                    <Description>Set Execution Policy 64 Bit</Description>
                    <Order>1</Order>
                    <RequiresUserInput>true</RequiresUserInput>
                </SynchronousCommand>
                <SynchronousCommand wcm:action="add">
                    <CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
                    <Description>Set Execution Policy 32 Bit</Description>
                    <Order>2</Order>
                    <RequiresUserInput>true</RequiresUserInput>
                </SynchronousCommand>
            </FirstLogonCommands>

"@

} # If
$unattendContent += @"
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                <NetworkLocation>Work</NetworkLocation>
                <ProtectYourPC>1</ProtectYourPC>
                <SkipUserOOBE>true</SkipUserOOBE>
                <SkipMachineOOBE>true</SkipMachineOOBE>
            </OOBE>
            <UserAccounts>
               <AdministratorPassword>
                  <Value>$($VM.AdministratorPassword)</Value>
                  <PlainText>true</PlainText>
               </AdministratorPassword>
            </UserAccounts>
            <RegisteredOrganization>$($domainName)</RegisteredOrganization>
            <RegisteredOwner>$($email)</RegisteredOwner>
            <DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
            <TimeZone>$($VM.TimeZone)</TimeZone>
        </component>
        <component name="Microsoft-Windows-ehome-reg-inf" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="NonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <RestartEnabled>true</RestartEnabled>
        </component>
        <component name="Microsoft-Windows-ehome-reg-inf" 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">
            <RestartEnabled>true</RestartEnabled>
        </component>
    </settings>
</unattend>
"@

    }

    return $unattendContent
}
#EndRegion './Private/Get-LabUnattendFileContent.ps1' 170
#Region './Private/Get-LabVMManagementIPAddress.ps1' 0
<#
    .SYNOPSIS
        Gets the Management IP Address for a running Lab VM.

    .DESCRIPTION
        This function will return the IPv4 address assigned to the network adapter that
        is connected to the Management switch for the specified VM. The VM must be
        running, otherwise an error will be thrown.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        $IPAddress = Get-LabVMManagementIPAddress -Lab $Lab -VM $VM[0]

    .OUTPUTS
        The IP Managment IP Address.
#>

function Get-LabVMManagementIPAddress
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    $managementSwitchName = Get-LabManagementSwitchName -Lab $Lab
    $managementAdapter = Get-VMNetworkAdapter -VMName $VM.Name |
        Where-Object -Property SwitchName -EQ -Value $managementSwitchName
    $managementAdapterIpAddresses = $managementAdapter.IPAddresses
    $managementAdapterIpAddress = $managementAdapterIpAddresses |
        Where-Object -FilterScript {
            $_.Contains('.')
        }

    if (-not $managementAdapterIpAddress) {
        $exceptionParameters = @{
            errorId = 'ManagmentIPAddressError'
            errorCategory = 'InvalidArgument'
            errorMessage = $($LocalizedData.ManagmentIPAddressError `
                -f $managementSwitchName,$VM.Name)
        }
        New-LabException @exceptionParameters
    } # if

    return $managementAdapterIpAddress
}
#EndRegion './Private/Get-LabVMManagementIPAddress.ps1' 59
#Region './Private/Initialize-LabBootVHD.ps1' 0
<#
    .SYNOPSIS
        Initialized a VM VHD for first boot by applying any required files to the image.

    .DESCRIPTION
        This function mounts a VM boot VHD image and applies the following files from the
        LabBuilder Files folder to it:
            1. Unattend.xml - a Windows Unattend.xml file.
            2. SetupComplete.cmd - the command file that gets run after the Windows OOBE is complete.
            3. SetupComplete.ps1 - this PowerShell script file that is run at the the end of the
                                    SetupComplete.cmd.
        The files should have already been prepared by the New-LabVMInitializationFile function.
        The VM VHD image should contain an installed copy of Windows still in OOBE mode.

        This function also applies optional MSU package files from the Lab resource folder if
        specified in the packages list in the VM.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A VMLab object pulled from the Lab Configuration file using Get-LabVM.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Initialize-LabBootVHD `
            -Lab $Lab `
            -VM $VMs[0] `
            -VMBootDiskPath $BootVHD[0]
        Prepare the boot VHD in for the first VM in the Lab c:\mylab\config.xml for initial boot.

    .OUTPUTS
        None.
#>

function Initialize-LabBootVHD
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter(Mandatory = $true)]
        [System.String]
        $VMBootDiskPath
    )

    # Get path to Lab
    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # Get Path to LabBuilder files
    [System.String] $VMLabBuilderFiles = $VM.LabBuilderFilesPath

    # Mount the VMs Boot VHD so that files can be loaded into it
    Write-LabMessage -Message $($LocalizedData.MountingVMBootDiskMessage `
        -f $VM.Name,$VMBootDiskPath)

    # Create a mount point for mounting the Boot VHD
    [System.String] $MountPoint = Join-Path `
        -Path $VMLabBuilderFiles `
        -ChildPath 'Mount'

    if (-not (Test-Path -Path $MountPoint -PathType Container))
    {
        $null = New-Item `
            -Path $MountPoint `
            -ItemType Directory
    }

    # Mount the VHD to the Mount point
    $null = Mount-WindowsImage `
        -ImagePath $VMBootDiskPath `
        -Path $MountPoint `
        -Index 1

    try
    {
        $Packages = $VM.Packages
        if ($VM.OSType -eq [LabOSType]::Nano)
        {
            # Now specify the Nano Server packages to add.
            [System.String] $NanoPackagesFolder = Join-Path `
                -Path $LabPath `
                -ChildPath 'NanoServerPackages'
            if (-not (Test-Path -Path $NanoPackagesFolder))
            {
                $exceptionParameters = @{
                    errorId = 'NanoServerPackagesFolderMissingError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.NanoServerPackagesFolderMissingError `
                    -f $NanoPackagesFolder)
                }
                New-LabException @exceptionParameters
            }
            # Add DSC Package to packages list if missing
            if ([System.String]::IsNullOrWhitespace($Packages))
            {
                $Packages = 'Microsoft-NanoServer-DSC-Package.cab'
            }
            else
            {
                if (@($Packages -split ',') -notcontains 'Microsoft-NanoServer-DSC-Package.cab')
                {
                    $Packages = "$Packages,Microsoft-NanoServer-DSC-Package.cab"
                } # if
            } # if
        } # if

        # Apply any listed packages to the Image
        if (-not [System.String]::IsNullOrWhitespace($Packages))
        {
            # Get the list of Lab Resource MSUs
            $ResourceMSUs = Get-LabResourceMSU `
                -Lab $Lab

            foreach ($Package in @($Packages -split ','))
            {
                if (([System.IO.Path]::GetExtension($Package) -eq '.cab') `
                    -and ($VM.OSType -eq [LabOSType]::Nano))
                {
                    # This is a Nano Server .CAB package
                    # Generate the path to the Nano Package
                    $PackagePath = Join-Path `
                        -Path $NanoPackagesFolder `
                        -ChildPath $Package

                    # Does it exist?
                    if (-not (Test-Path -Path $PackagePath))
                    {
                        $exceptionParameters = @{
                            errorId = 'NanoPackageNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.NanoPackageNotFoundError `
                            -f $PackagePath)
                        }
                        New-LabException @exceptionParameters
                    }

                    # Add the package
                    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
                        -f $VM.Name,$Package,$PackagePath)

                    $null = Add-WindowsPackage `
                        -PackagePath $PackagePath `
                        -Path $MountPoint

                    # Generate the path to the Nano Language Package
                    $PackageLangFile = $Package -replace '.cab',"_$($script:NanoPackageCulture).cab"
                    $PackageLangFile = Join-Path `
                        -Path $NanoPackagesFolder `
                        -ChildPath "$($script:NanoPackageCulture)\$PackageLangFile"

                    # Does it exist?
                    if (-not (Test-Path -Path $PackageLangFile))
                    {
                        $exceptionParameters = @{
                            errorId = 'NanoPackageNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.NanoPackageNotFoundError `
                            -f $PackageLangFile)
                        }
                        New-LabException @exceptionParameters
                    }

                    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
                        -f $VM.Name,$Package,$PackageLangFile)

                    # Add the package
                    $null = Add-WindowsPackage `
                        -PackagePath $PackageLangFile `
                        -Path $MountPoint
                }
                else
                {
                    # Tihs is a ResourceMSU type package
                    [System.Boolean] $Found = $false
                    foreach ($ResourceMSU in $ResourceMSUs)
                    {
                        if ($ResourceMSU.Name -eq $Package)
                        {
                            # Found the package
                            $Found = $true
                            break
                        } # if
                    } # foreach
                    if (-not $Found)
                    {
                        $exceptionParameters = @{
                            errorId = 'PackageNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.PackageNotFoundError `
                            -f $Package)
                        }
                        New-LabException @exceptionParameters
                    } # if

                    $PackagePath = $ResourceMSU.Filename
                    if (-not (Test-Path -Path $PackagePath))
                    {
                        $exceptionParameters = @{
                            errorId = 'PackageMSUNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.PackageMSUNotFoundError `
                            -f $Package,$PackagePath)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    # Apply a Package
                    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
                        -f $VM.Name,$Package,$PackagePath)

                    $null = Add-WindowsPackage `
                        -PackagePath $PackagePath `
                        -Path $MountPoint
                } # if
            } # foreach
        } # if
    }
    catch
    {
        # Dismount Disk Image before throwing exception
        Write-LabMessage -Message $($LocalizedData.DismountingVMBootDiskMessage `
            -f $VM.Name,$VMBootDiskPath)
        $null = Dismount-WindowsImage -Path $MountPoint -Save
        $null = Remove-Item -Path $MountPoint -Recurse -Force

        Throw $_
    } # try

    # Create the scripts folder where setup scripts will be put
    $null = New-Item `
        -Path "$MountPoint\Windows\Setup\Scripts" `
        -ItemType Directory

    # Create the ODJ folder where Offline domain join files can be put
    $null = New-Item `
        -Path "$MountPoint\Windows\Setup\ODJFiles" `
        -ItemType Directory

    # Apply an unattended setup file
    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
        -f $VM.Name,'Unattend','Unattend.xml')

    if (-not (Test-Path -Path "$MountPoint\Windows\Panther" -PathType Container))
    {
        Write-LabMessage -Message $($LocalizedData.CreatingVMBootDiskPantherFolderMessage `
            -f $VM.Name)

        $null = New-Item `
            -Path "$MountPoint\Windows\Panther" `
            -ItemType Directory
    } # if
    $null = Copy-Item `
        -Path (Join-Path -Path $VMLabBuilderFiles -ChildPath 'Unattend.xml') `
        -Destination "$MountPoint\Windows\Panther\Unattend.xml" `
        -Force

    # If a Certificate PFX file is available, copy it into the c:\Windows
    # folder of the VM.
    $CertificatePfxPath = Join-Path `
        -Path $VMLabBuilderFiles `
        -ChildPath $script:DSCEncryptionPfxCert
    if (Test-Path -Path $CertificatePfxPath)
    {
        # Apply the CMD Setup Complete File
        Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
            -f $VM.Name,'Credential Certificate PFX',$script:DSCEncryptionPfxCert)
        $null = Copy-Item `
            -Path $CertificatePfxPath `
            -Destination "$MountPoint\Windows\$script:DSCEncryptionPfxCert" `
            -Force
    }

    # Apply the CMD Setup Complete File
    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
        -f $VM.Name,'Setup Complete CMD','SetupComplete.cmd')
    $null = Copy-Item `
        -Path (Join-Path -Path $VMLabBuilderFiles -ChildPath 'SetupComplete.cmd') `
        -Destination "$MountPoint\Windows\Setup\Scripts\SetupComplete.cmd" `
        -Force

    # Apply the PowerShell Setup Complete file
    Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
        -f $VM.Name,'Setup Complete PowerShell','SetupComplete.ps1')
    $null = Copy-Item `
        -Path (Join-Path -Path $VMLabBuilderFiles -ChildPath 'SetupComplete.ps1') `
        -Destination "$MountPoint\Windows\Setup\Scripts\SetupComplete.ps1" `
        -Force

    # Apply the Certificate Generator script if not a Nano Server
    if ($VM.OSType -ne [LabOSType]::Nano)
    {
        $CertGenFilename = Split-Path -Path $script:SupportGertGenPath -Leaf
        Write-LabMessage -Message $($LocalizedData.ApplyingVMBootDiskFileMessage `
            -f $VM.Name,'Certificate Create Script',$CertGenFilename)
        $null = Copy-Item `
            -Path $script:SupportGertGenPath `
            -Destination "$MountPoint\Windows\Setup\Scripts\"`
            -Force
    }

    # Dismount the VHD in preparation for boot
    Write-LabMessage -Message $($LocalizedData.DismountingVMBootDiskMessage `
        -f $VM.Name,$VMBootDiskPath)
    $null = Dismount-WindowsImage -Path $MountPoint -Save
    $null = Remove-Item -Path $MountPoint -Recurse -Force
}
#EndRegion './Private/Initialize-LabBootVHD.ps1' 313
#Region './Private/Initialize-LabDSC.ps1' 0
<#
    .SYNOPSIS
        This function prepares all files require to configure a VM using Desired State
        Configuration (DSC).

    .DESCRIPTION
        Calling this function will cause the LabBuilder folder to be populated/updated
        with all files required to configure a Virtual Machine with DSC.
        This includes:
            1. Required DSC Resouce Modules.
            2. DSC Credential Encryption certificate.
            3. DSC Configuration files.
            4. DSC MOF Files for general config and for LCM config.
            5. Start up scripts.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Initialize-LabDSC -Lab $Lab -VM $VMs[0]
        Prepares all files required to start up Desired State Configuration for the
        first VM in the Lab c:\mylab\config.xml for DSC start up.

    .OUTPUTS
        None.
#>

function Initialize-LabDSC
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    # Are there any DSC Settings to manage?
    Update-LabDSC -Lab $Lab -VM $VM

    # Generate the DSC Start up Script file
    Set-LabDSC -Lab $Lab -VM $VM
}
#EndRegion './Private/Initialize-LabDSC.ps1' 51
#Region './Private/Initialize-LabManagementSwitch.ps1' 0
<#
    .SYNOPSIS
        Create the LabBuilder Management Network switch and assign VLAN

    .DESCRIPTION
        Each lab needs a unique private management switch created for it.
        All Virtual Machines in the Lab are connected to the switch.
        This function creates the virtual switch and attaches an adapter
        to it and assigns it to a VLAN.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        Initialize-LabManagementSwitch -Lab $Lab
        Creates or updates the Management Switch for the Lab c:\mylab\config.xml.

    .OUTPUTS
        None.
#>

function Initialize-LabManagementSwitch
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab
    )

    # Used by host to communicate with Lab VMs
    $managementSwitchName = Get-LabManagementSwitchName -Lab $Lab

    Write-LabMessage -Message $($LocalizedData.InitializingLabManagementVirtualNetworkMesage `
        -f $managementSwitchName)

    if ($Lab.labbuilderconfig.switches.ManagementVlan)
    {
        $requiredManagementVlan = $Lab.labbuilderconfig.switches.ManagementVlan
    }
    else
    {
        $requiredManagementVlan = $script:DefaultManagementVLan
    }

    $managementSwitch = Get-VMSwitch | Where-Object -Property Name -eq $managementSwitchName

    if ($managementSwitch.Count -eq 0)
    {
        $null = New-VMSwitch `
            -SwitchType Internal `
            -Name $managementSwitchName `
            -ErrorAction Stop

        Write-LabMessage -Message $($LocalizedData.CreatingLabManagementSwitchMessage `
                -f $managementSwitchName, $requiredManagementVlan)
    }

    # Check the Vlan ID of the adapter on the switch
    $existingManagementAdapter = Get-VMNetworkAdapter `
        -ManagementOS `
        -Name $managementSwitchName `
        -SwitchName $managementSwitchName `
        -ErrorAction SilentlyContinue

    if ($null -eq $existingManagementAdapter)
    {
        $existingManagementAdapter = Add-VMNetworkAdapter `
            -ManagementOS `
            -Name $managementSwitchName `
            -SwitchName $managementSwitchName `
            -ErrorAction Stop
    }

    $existingManagementAdapterVlan = Get-VMNetworkAdapterVlan `
        -VMNetworkAdapter $existingManagementAdapter

    $existingManagementVlan = $existingManagementAdapterVlan.AccessVlanId

    if ($existingManagementVlan -ne $requiredManagementVlan)
    {
        Write-LabMessage -Message $($LocalizedData.UpdatingLabManagementSwitchMessage `
            -f $managementSwitchName, $requiredManagementVlan)

        Set-VMNetworkAdapterVlan `
            -VMNetworkAdapter $existingManagementAdapter `
            -Access `
            -VlanId $requiredManagementVlan `
            -ErrorAction Stop
    }
}
#EndRegion './Private/Initialize-LabManagementSwitch.ps1' 92
#Region './Private/Initialize-LabVHD.ps1' 0
<#
    .SYNOPSIS
        This function mounts the VHDx passed and ensures it is OK to be written to.

    .DESCRIPTION
        The function checks that the disk has been paritioned and that it contains
        a volume that has been formatted.

        This function will work for the following situations:
        0. VHDx is not mounted.
        1. VHDx is not initialized and PartitionStyle is passed.
        2. VHDx is initialized but has 0 partitions and FileSystem is passed.
        3. VHDx has 1 partition but 0 volumes and FileSystem is passed.
        4. VHDx has 1 partition and 1 volume that is unformatted and FileSystem is passed.
        5. VHDx has 1 partition and 1 volume that is formatted.

        If the VHDx is any other state an exception will be thrown.

        If the FileSystemLabel passed is different to the current label then it will
        be updated.

        This function will not change the File System and/or Partition Type on the VHDx
        if it is different to the values provided.

    .PARAMETER Path
        This is the path to the VHD/VHDx file to mount and initialize.

    .PARAMETER PartitionStyle
        The Partition Style to set an uninitialized VHD/VHDx to. It can be MBR or GPT.
        If it is not passed and the VHD is not initialized then an exception will be
        thrown.

    .PARAMETER FileSystem
        The File System to format the new parition with on an VHD/VHDx. It can be
        FAT, FAT32, exFAT, NTFS, ReFS.
        If it is not passed and the VHD does not contain any formatted volumes then
        an exception will be thrown.

    .PARAMETER FileSystemLabel
        This parameter will allow the File System Label of the disk to be changed to this
        value.

    .PARAMETER DriveLetter
        Setting this parameter to a drive letter that is not in use will cause the VHD
        to be assigned to this drive letter.

    .PARAMETER AccessPath
        Setting this parameter to an existing folder will cause the VHD to be assigned
        to the AccessPath defined. The folder must already exist otherwise an exception
        will be thrown.

    .EXAMPLE
        Initialize-LabVHD -Path c:\VMs\Tools.VHDx -AccessPath c:\mount
        The VHDx c:\VMs\Tools.VHDx will be mounted and and assigned to the c:\mount folder
        if it is initialized and contains a formatted partition.

    .EXAMPLE
        Initialize-LabVHD -Path c:\VMs\Tools.VHDx -PartitionStyle GPT -FileSystem NTFS
        The VHDx c:\VMs\Tools.VHDx will be mounted and initialized with GPT if not already
        initialized. It will also be partitioned and formatted with NTFS if no partitions
        already exist.

    .EXAMPLE
        Initialize-LabVHD `
            -Path c:\VMs\Tools.VHDx `
            -PartitionStyle GPT `
            -FileSystem NTFS `
            -FileSystemLabel ToolsDisk
            -DriveLetter X
        The VHDx c:\VMs\Tools.VHDx will be mounted and initialized with GPT if not already
        initialized. It will also be partitioned and formatted with NTFS if no partitions
        already exist. The File System label will also be set to ToolsDisk and the disk
        will be mounted to X drive.

    .OUTPUTS
        It will return the Volume object that can then be mounted to a Drive Letter
        or path.
#>

function Initialize-LabVHD
{
    [OutputType([Microsoft.Management.Infrastructure.CimInstance])]
    [CmdletBinding(DefaultParameterSetName = 'AssignDriveLetter')]
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Path,

        [Parameter()]
        [LabPartitionStyle]
        $PartitionStyle,

        [Parameter()]
        [LabFileSystem]
        $FileSystem,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $FileSystemLabel,

        [Parameter(ParameterSetName = 'DriveLetter')]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $DriveLetter,

        [Parameter(ParameterSetName = 'AccessPath')]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $AccessPath
    )

    # Check file exists
    if (-not (Test-Path -Path $Path))
    {
        $exceptionParameters = @{
            errorId = 'FileNotFoundError'
            errorCategory = 'InvalidArgument'
            errorMessage = $($LocalizedData.FileNotFoundError `
            -f "VHD",$Path)
        }
        New-LabException @exceptionParameters
    } # if

    # Check disk is not already mounted
    $VHD = Get-VHD `
        -Path $Path
    if (-not $VHD.Attached)
    {
        Write-LabMessage -Message ($LocalizedData.InitializeVHDMountingMessage `
            -f $Path)

        $null = Mount-VHD `
            -Path $Path `
            -ErrorAction Stop
        $VHD = Get-VHD `
            -Path $Path
    } # if

    # Check partition style
    $DiskNumber = $VHD.DiskNumber
    if ((Get-Disk -Number $DiskNumber).PartitionStyle -eq 'RAW')
    {
        if (-not $PartitionStyle)
        {
            $exceptionParameters = @{
                errorId = 'InitializeVHDNotInitializedError'
                errorCategory = 'InvalidArgument'
                errorMessage = $($LocalizedData.InitializeVHDNotInitializedError `
                -f $Path)
            }
            New-LabException @exceptionParameters
        } # if
        Write-LabMessage -Message ($LocalizedData.InitializeVHDInitializingMessage `
            -f $Path,$PartitionStyle)

        $null = Initialize-Disk `
            -Number $DiskNumber `
            -PartitionStyle $PartitionStyle `
            -ErrorAction Stop
    } # if

    # Check for a partition that is not 'reserved'
    $Partitions = @(Get-Partition `
        -DiskNumber $DiskNumber `
        -ErrorAction SilentlyContinue)
    if (-not ($Partitions) `
        -or (($Partitions | Where-Object -Property Type -ne 'Reserved').Count -eq 0))
    {
        Write-LabMessage -Message ($LocalizedData.InitializeVHDCreatePartitionMessage `
            -f $Path)

        $Partitions = @(New-Partition `
            -DiskNumber $DiskNumber `
            -UseMaximumSize `
            -ErrorAction Stop)
    } # if

    # Find the best partition to work with
    # This will usually be the one just created if it was
    # Otherwise we'll try and match by FileSystem and then
    # format and failing that the first partition.
    foreach ($Partition in $Partitions)
    {
        $VolumeFileSystem = (Get-Volume `
            -Partition $Partition).FileSystem
        if ($FileSystem)
        {
            if (-not [System.String]::IsNullOrWhitespace($VolumeFileSystem))
            {
                # Found a formatted partition
                $FoundFormattedPartition = $Partition
            } # if
            if ($FileSystem -eq $VolumeFileSystem)
            {
                # Found a parition with a matching file system
                $FoundPartition = $Partition
                break
            } # if
        }
        else
        {
            if (-not [System.String]::IsNullOrWhitespace($VolumeFileSystem))
            {
                # Found an formatted partition
                $FoundFormattedPartition = $Partition
                break
            } # if
        } # if
    } # foreach
    if ($FoundPartition)
    {
        # Use the formatted partition
        $Partition = $FoundPartition
    }
    elseif ($FoundFormattedPartition)
    {
        # An unformatted partition was found
        $Partition = $FoundFormattedPartition
    }
    else
    {
        # There are no formatted partitions so use the first one
        $Partition = $Partitions[0]
    } # if

    $PartitionNumber = $Partition.PartitionNumber

    # Check for volume
    $Volume = Get-Volume `
        -Partition $Partition

    # Check for file system
    if ([System.String]::IsNullOrWhitespace($Volume.FileSystem))
    {
        # This volume is not formatted
        if (-not $FileSystem)
        {
            # A File System wasn't specified so can't continue
            $exceptionParameters = @{
                errorId = 'InitializeVHDNotFormattedError'
                errorCategory = 'InvalidArgument'
                errorMessage = $($LocalizedData.InitializeVHDNotFormattedError `
                -f $Path)
            }
            New-LabException @exceptionParameters
        }

        # Format the volume
        Write-LabMessage -Message ($LocalizedData.InitializeVHDFormatVolumeMessage `
            -f $Path,$FileSystem,$PartitionNumber)
        $FormatProperties = @{
            InputObject = $Volume
            FileSystem = $FileSystem
        }
        if ($FileSystemLabel)
        {
            $FormatProperties += @{
                NewFileSystemLabel = $FileSystemLabel
            }
        }
        $Volume = Format-Volume `
            @FormatProperties `
            -ErrorAction Stop
    }
    else
    {
        # Check the File System Label
        if (($FileSystemLabel) -and `
            ($Volume.FileSystemLabel -ne $FileSystemLabel))
        {
            Write-LabMessage -Message ($LocalizedData.InitializeVHDSetLabelVolumeMessage `
                -f $Path,$FileSystemLabel)
            $Volume = Set-Volume `
                -InputObject $Volume `
                -NewFileSystemLabel $FileSystemLabel `
                -ErrorAction Stop
        }
    }

    # Assign an access path or Drive letter
    if ($DriveLetter -or $AccessPath)
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'DriveLetter'
            {
                # Mount the partition to a Drive Letter
                $null = Set-Partition `
                    -DiskNumber $Disknumber `
                    -PartitionNumber $PartitionNumber `
                    -NewDriveLetter $DriveLetter `
                    -ErrorAction Stop

                $Volume = Get-Volume `
                    -Partition $Partition

                Write-LabMessage -Message ($LocalizedData.InitializeVHDDriveLetterMessage `
                    -f $Path,$DriveLetter.ToUpper())
            }
            'AccessPath'
            {
                # Check the Access folder exists
                if (-not (Test-Path -Path $AccessPath -Type Container))
                {
                    $exceptionParameters = @{
                        errorId = 'InitializeVHDAccessPathNotFoundError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.InitializeVHDAccessPathNotFoundError `
                        -f $Path,$AccessPath)
                    }
                    New-LabException @exceptionParameters
                }

                # Add the Partition Access Path
                $null = Add-PartitionAccessPath `
                    -DiskNumber $DiskNumber `
                    -PartitionNumber $partitionNumber `
                    -AccessPath $AccessPath `
                    -ErrorAction Stop

                Write-LabMessage -Message ($LocalizedData.InitializeVHDAccessPathMessage `
                    -f $Path,$AccessPath)
            }
        }
    }

    # Return the Volume to the pipeline
    return $Volume
}
#EndRegion './Private/Initialize-LabVHD.ps1' 332
#Region './Private/Initialize-LabVMPath.ps1' 0
<#
    .SYNOPSIS
        Creates the folder structure that will contain a Lab Virtual Machine.

    .DESCRIPTION
        Creates a standard Hyper-V Virtual Machine folder structure as well as additional folders
        for containing configuration files for DSC.

    .PARAMETER vmpath
        The path to the folder where the Virtual Machine files are stored.

    .EXAMPLE
        Initialize-LabVMPath -VMPath 'c:\VMs\Lab\Virtual Machine 1'
        The command will create the Virtual Machine structure for a Lab VM in the folder:
        'c:\VMs\Lab\Virtual Machine 1'

    .OUTPUTS
        None.
#>

function Initialize-LabVMPath
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $VMPath
    )

    if (-not (Test-Path -Path $VMPath))
    {
        $null = New-Item `
            -Path $VMPath `
            -ItemType Directory
    } # if

    if (-not (Test-Path -Path "$VMPath\Virtual Machines"))
    {
        $null = New-Item `
            -Path "$VMPath\Virtual Machines" `
            -ItemType Directory
    } # if

    if (-not (Test-Path -Path "$VMPath\Virtual Hard Disks"))
    {
        $null = New-Item `
        -Path "$VMPath\Virtual Hard Disks" `
        -ItemType Directory
    } # if

    if (-not (Test-Path -Path "$VMPath\LabBuilder Files"))
    {
        $null = New-Item `
            -Path "$VMPath\LabBuilder Files" `
            -ItemType Directory
    } # if

    if (-not (Test-Path -Path "$VMPath\LabBuilder Files\DSC Modules"))
    {
        $null = New-Item `
            -Path "$VMPath\LabBuilder Files\DSC Modules" `
            -ItemType Directory
    } # if
}
#EndRegion './Private/Initialize-LabVMPath.ps1' 66
#Region './Private/Install-LabHyperV.ps1' 0
<#
    .SYNOPSIS
        Ensures the Hyper-V features are installed onto the system.

    .DESCRIPTION
        If the Hyper-V features are not installed onto this system they will be installed.

    .EXAMPLE
        Install-LabHyperV
        Installs the appropriate Hyper-V features if they are not currently installed.

    .OUTPUTS
        None
#>

function Install-LabHyperV
{
    [CmdLetBinding()]
    param
    (
    )

    # Install Hyper-V Components
    if ((Get-CimInstance Win32_OperatingSystem).ProductType -eq 1)
    {
        # Desktop OS
        [Array] $feature = Get-WindowsOptionalFeature -Online -FeatureName '*Hyper-V*' `
            | Where-Object -Property State -Eq 'Disabled'
        if ($feature.Count -gt 0 )
        {
            Write-LabMessage -Message ($LocalizedData.InstallingHyperVComponentsMesage `
                    -f 'Desktop')
            $feature.Foreach( {
                    Enable-WindowsOptionalFeature -Online -FeatureName $_.FeatureName
                } )
        }
    }
    Else
    {
        # Server OS
        [Array] $feature = Get-WindowsFeature -Name Hyper-V `
            | Where-Object -Property Installed -EQ $false
        if ($feature.Count -gt 0 )
        {
            Write-LabMessage -Message ($LocalizedData.InstallingHyperVComponentsMesage `
                    -f 'Desktop')
            $feature.Foreach( {
                    Install-WindowsFeature -IncludeAllSubFeature -IncludeManagementTools -Name $_.Name
                } )
        }
    }
}
#EndRegion './Private/Install-LabHyperV.ps1' 52
#Region './Private/Install-LabPackageProvider.ps1' 0
<#
    .SYNOPSIS
        Ensures the Package Providers required by LabBuilder are installed.

    .DESCRIPTION
        This function will check that both the NuGet and the PowerShellGet package
        providers are installed.
        If either of them are missing the function will attempt to install them.

    .EXAMPLE
        Install-LabPackageProvider
        Ensures the required Package Providers for LabBuilder are installed.

    .OUTPUTS
        None
#>

function Install-LabPackageProvider
{
    [CmdLetBinding(SupportsShouldProcess = $true,
        ConfirmImpact = 'High')]
    param
    (
        [Parameter()]
        [Switch]
        $Force
    )

    $requiredPackageProviders = @('PowerShellGet', 'NuGet')
    $currentPackageProviders = Get-PackageProvider `
        -ListAvailable `
        -ErrorAction Stop

    foreach ($requiredPackageProvider in $requiredPackageProviders)
    {
        $packageProvider = $currentPackageProviders |
            Where-Object { $_.Name -eq $requiredPackageProvider }

        if (-not $packageProvider)
        {
            # The Package provider is not installed so install it
            if ($Force -or $PSCmdlet.ShouldProcess( 'LocalHost', `
                    ($LocalizedData.ShouldInstallPackageProvider `
                            -f $packageProvider )))
            {
                Write-LabMessage -Message ($LocalizedData.InstallPackageProviderMessage `
                        -f $requiredPackageProvider)

                $null = Install-PackageProvider `
                    -Name $requiredPackageProvider `
                    -ForceBootstrap `
                    -Force `
                    -ErrorAction Stop
            }
            else
            {
                # Can't continue if the package provider is not installed.
                $exceptionParameters = @{
                    errorId       = 'PackageProviderNotInstalledError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.PackageProviderNotInstalledError `
                            -f $requiredPackageProvider)
                }
                New-LabException @exceptionParameters
            } # if
        } # if
    } # foreach
}
#EndRegion './Private/Install-LabPackageProvider.ps1' 68
#Region './Private/Invoke-LabDownloadAndUnzipFile.ps1' 0
<#
    .SYNOPSIS
        Download the a file to a folder and optionally unzip it.

    .DESCRIPTION
        If the file is a zip file the file will be downloaded to a temporary
        working folder and then unzipped to the destination, otherwise it
        will be downloaded straight to the destination folder.
#>

function Invoke-LabDownloadAndUnzipFile
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $URL,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $DestinationPath
    )

    $fileName = [System.IO.Path]::GetFileName($URL)

    if (-not (Test-Path -Path $DestinationPath))
    {
        $exceptionParameters = @{
            errorId       = 'DownloadFolderDoesNotExistError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.DownloadFolderDoesNotExistError `
                    -f $DestinationPath, $fileName)
        }
        New-LabException @exceptionParameters
    }

    $extension = [System.IO.Path]::GetExtension($fileName)

    if ($extension -eq '.zip')
    {
        # Download to a temp folder and unzip
        $downloadPath = Join-Path -Path $script:WorkingFolder -ChildPath $fileName
    }
    else
    {
        # Download to a temp folder and unzip
        $downloadPath = Join-Path -Path $DestinationPath -ChildPath $fileName
    }

    Write-LabMessage -Message ($LocalizedData.DownloadingFileMessage `
            -f $fileName, $URL, $downloadPath)

    try
    {
        Invoke-WebRequest `
            -Uri $URL `
            -OutFile $downloadPath `
            -ErrorAction Stop
    }
    catch
    {
        $exceptionParameters = @{
            errorId       = 'FileDownloadError'
            errorCategory = 'InvalidOperation'
            errorMessage  = $($LocalizedData.FileDownloadError -f $fileName, $URL, $_.Exception.Message)
        }
        New-LabException @exceptionParameters
    } # try

    if ($extension -eq '.zip')
    {
        Write-LabMessage -Message ($LocalizedData.ExtractingFileMessage `
                -f $fileName, $downloadPath)

        # Extract this to the destination folder
        try
        {
            Expand-Archive `
                -Path $downloadPath `
                -DestinationPath $DestinationPath `
                -Force `
                -ErrorAction Stop
        }
        catch
        {
            $exceptionParameters = @{
                errorId       = 'FileExtractError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.FileExtractError -f $fileName, $_.Exception.Message)
            }
            New-LabException @exceptionParameters
        }
        finally
        {
            # Remove the downloaded zip file
            Remove-Item -Path $downloadPath
        } # try
    }
}
#EndRegion './Private/Invoke-LabDownloadAndUnzipFile.ps1' 102
#Region './Private/Invoke-LabDownloadResourceModule.ps1' 0
<#
    .SYNOPSIS
        Downloads a resource module.

    .DESCRIPTION
        It will download a specific resource module, either from PowerShell Gallery
        or from a URL if the module does not already exist.

    .PARAMETER Name
        Contains the Name of the module to download.

    .PARAMETER URL
        If this parameter is specified, the resource module will be downloaded from a URL
        rather than via PowerShell Gallery. This is a the URL to use to download a zip
        file containing this resource module.

    .PARAMETER Folder
        If this resource module is downloaded using a URL, this is the folder in the zip
        file that contains the resource and will need to be renamed to the name of the
        resource.

    .PARAMETER RequiredVersion
        This is the required version of the Resource Module that is required.
        If this version is not installed the a new version will be downloaded.

    .PARAMETER MinimumVersion
        This is the minimum version of the Resource Module that is required.
        If at least this version is not installed then a new version will be downloaded.

    .EXAMPLE
        Invoke-LabDownloadResourceModule `
            -Name NetworkingDsc `
            -RequiredVersion 2.7.0.0
        Downloads the Resource Module xNetowrking version 2.7.0.0

    .OUTPUTS
        None.
#>

function Invoke-LabDownloadResourceModule
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Name,

        [Parameter(
            position = 2)]
        [System.String]
        $URL,

        [Parameter(
            position = 3)]
        [System.String]
        $Folder,

        [Parameter(
            position = 4)]
        [System.String]
        $RequiredVersion,

        [Parameter(
            position = 5)]
        [System.String]
        $MinimumVersion
    )

    $installedModules = @(Get-Module -ListAvailable)

    # Determine a query that will be used to decide if the module is already installed
    if ($RequiredVersion)
    {
        [ScriptBlock] $Query = {
            ($_.Name -eq $Name) -and ($_.Version -eq $RequiredVersion)
        }

        $versionMessage = $RequiredVersion
    }
    elseif ($MinimumVersion)
    {
        [ScriptBlock] $Query = {
            ($_.Name -eq $Name) -and ($_.Version -ge $MinimumVersion)
        }

        $versionMessage = "min ${MinimumVersion}"
    }
    else
    {
        [ScriptBlock] $Query = {
            $_.Name -eq $Name
        }

        $versionMessage = 'any version'
    }

    # Is the module installed?
    if ($installedModules.Where($Query).Count -eq 0)
    {
        Write-LabMessage -Message ($LocalizedData.ModuleNotInstalledMessage `
                -f $Name, $versionMessage)

        # If a URL was specified, download this module via HTTP
        if ($URL)
        {
            # The module is not installed - so download it
            # This is usually for downloading modules directly from github
            Write-LabMessage -Message ($LocalizedData.DownloadingLabResourceWebMessage `
                    -f $Name, $versionMessage, $URL)

            $modulesFolder = "$($ENV:ProgramFiles)\WindowsPowerShell\Modules\"

            Invoke-LabDownloadAndUnzipFile `
                -URL $URL `
                -DestinationPath $modulesFolder `
                -ErrorAction Stop

            if ($Folder)
            {
                # This zip file contains a folder that is not the name of the module so it must be
                # renamed. This is usually the case with source downloaded directly from GitHub
                $modulePath = Join-Path -Path $modulesFolder -ChildPath $Name

                if (Test-Path -Path $modulePath)
                {
                    Remove-Item -Path $modulePath -Recurse -Force
                }

                Rename-Item `
                    -Path (Join-Path -Path $modulesFolder -ChildPath $Folder) `
                    -NewName $Name `
                    -Force
            } # if

            Write-LabMessage -Message ($LocalizedData.InstalledLabResourceWebMessage `
                    -f $Name, $versionMessage, $modulePath)
        }
        else
        {
            # Install the package via PowerShellGet from the PowerShellGallery
            # Make sure the Nuget Package provider is initialized.
            $null = Get-PackageProvider `
                -name nuget `
                -ForceBootStrap `
                -Force

            # Make sure PSGallery is trusted
            Set-PSRepository `
                -Name PSGallery `
                -InstallationPolicy Trusted

            # Install the module
            $installModuleParameters = [PSObject] @{ Name = $Name }

            if ($RequiredVersion)
            {
                # Is a specific module version required?
                $installModuleParameters += [PSObject] @{
                    RequiredVersion = $RequiredVersion
                }
            }
            elseif ($MinimumVersion)
            {
                # Is a specific module version minimum version?
                $installModuleParameters += [PSObject] @{
                    MinimumVersion = $MinimumVersion
                }
            }

            try
            {
                Install-Module @installModuleParameters -Force -ErrorAction Stop
            }
            catch
            {
                $exceptionParameters = @{
                    errorId       = 'ModuleNotAvailableError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.ModuleNotAvailableError `
                            -f $Name, $versionMessage, $_.Exception.Message)
                }
                New-LabException @exceptionParameters
            }
        } # If
    } # If
}
#EndRegion './Private/Invoke-LabDownloadResourceModule.ps1' 190
#Region './Private/New-LabCredential.ps1' 0
<#
    .SYNOPSIS
        Generates a credential object from a username and password.
#>

function New-LabCredential()
{
    [CmdletBinding()]
    [OutputType([PSCredential])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Username,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Password
    )

    $credential = New-Object `
        -TypeName System.Management.Automation.PSCredential `
        -ArgumentList ($Username, (ConvertTo-SecureString $Password -AsPlainText -Force))

    return $credential
}
#EndRegion './Private/New-LabCredential.ps1' 28
#Region './Private/New-LabException.ps1' 0
<#
    .SYNOPSIS
        Throws a custom exception.

    .DESCRIPTION
        This cmdlet throws a terminating or non-terminating exception.

    .PARAMETER errorId
        The Id of the exception.

    .PARAMETER errorCategory
        The category of the exception. It must be a valid [System.Management.Automation.ErrorCategory]
        value.

    .PARAMETER errorMessage
        The exception message.

    .PARAMETER terminate
        This switch will cause the exception to terminate the cmdlet.

    .EXAMPLE
        $exceptionParameters = @{
            errorId = 'ConnectionFailure'
            errorCategory = 'ConnectionError'
            errorMessage = 'Could not connect'
        }
        New-LabException @exceptionParameters
        Throw a ConnectionError exception with the message 'Could not connect'.

    .OUTPUTS
        None
#>

function New-LabException
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ErrorId,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorCategory]
        $ErrorCategory,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ErrorMessage,

        [Switch]
        $Terminate
    )

    $exception = New-Object -TypeName System.Exception `
        -ArgumentList $errorMessage
    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
        -ArgumentList $exception, $errorId, $errorCategory, $null

    if ($Terminate)
    {
        # This is a terminating exception.
        throw $errorRecord
    }
    else
    {
        # Note: Although this method is called ThrowTerminatingError, it doesn't terminate.
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}
#EndRegion './Private/New-LabException.ps1' 70
#Region './Private/New-LabHostSelfSignedCertificate.ps1' 0
<#
    .SYNOPSIS
        Generate a new credential encryption certificate on the Host for a VM.

    .DESCRIPTION
        This function will create a new self-signed certificate on the host that can be uploaded
        to the VM that it is created for. The certificate will be created in the LabBuilder files
        folder for the specified VM.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        New-LabHostSelfSignedCertificate -Lab $Lab -VM $VMs[0]
        Causes a new self-signed certificate for the VM and stores it to the Labbuilder files folder
        of th VM.

    .OUTPUTS
        The path to the certificate file that was created.
#>

function New-LabHostSelfSignedCertificate
{
    [CmdLetBinding()]
    [OutputType([System.IO.FileInfo])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    $certificateFriendlyName = $script:DSCCertificateFriendlyName
    $certificateSubject = "CN=$($VM.ComputerName)"

    # Create the self-signed certificate for the destination VM
    . $script:SupportGertGenPath
    New-SelfsignedCertificateEx `
        -Subject $certificateSubject `
        -EKU 'Document Encryption','Server Authentication','Client Authentication' `
        -KeyUsage 'DigitalSignature, KeyEncipherment, DataEncipherment' `
        -SAN $VM.ComputerName `
        -FriendlyName $certificateFriendlyName `
        -Exportable `
        -StoreLocation 'LocalMachine' `
        -StoreName 'My' `
        -KeyLength $script:SelfSignedCertKeyLength `
        -ProviderName $script:SelfSignedCertProviderName `
        -AlgorithmName $script:SelfSignedCertAlgorithmName `
        -SignatureAlgorithm $script:SelfSignedCertSignatureAlgorithm `
        -ErrorAction Stop

    # Locate the newly created certificate
    $certificate = Get-ChildItem -Path cert:\LocalMachine\My `
        | Where-Object {
            ($_.FriendlyName -eq $certificateFriendlyName) `
            -and ($_.Subject -eq $certificateSubject)
        } | Select-Object -First 1

    # Export the certificate with the Private key in
    # preparation for upload to the VM
    $certificatePassword = ConvertTo-SecureString `
        -String $script:DSCCertificatePassword `
        -Force `
        -AsPlainText
    $certificatePfxDestination = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath $script:DSCEncryptionPfxCert
    $null = Export-PfxCertificate `
        -FilePath $certificatePfxDestination `
        -Cert $certificate `
        -Password $certificatePassword `
        -ErrorAction Stop

    # Export the certificate without a private key
    $certificateDestination = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath $script:DSCEncryptionCert
    $null = Export-Certificate `
        -Type CERT `
        -FilePath $certificateDestination `
        -Cert $certificate `
        -ErrorAction Stop

    # Remove the certificate from the Local Machine store
    $certificate | Remove-Item

    return (Get-Item -Path $certificateDestination)
}
#EndRegion './Private/New-LabHostSelfSignedCertificate.ps1' 100
#Region './Private/New-LabVMInitializationFile.ps1' 0
<#
    .SYNOPSIS
        Prepares the the files for initializing a new VM.

    .DESCRIPTION
        This function creates the following files in the LabBuilder Files for the a VM in preparation
        for them to be applied to the VM VHD before it is booted up for the first time:
            1. Unattend.xml - a Windows Unattend.xml file.
            2. SetupComplete.cmd - the command file that gets run after the Windows OOBE is complete.
            3. SetupComplete.ps1 - this PowerShell script file that is run at the the end of the
                                SetupComplete.cmd.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        New-LabVMInitializationFile -Lab $Lab -VM $VMs[0]
        Prepare the first VM in the Lab c:\mylab\config.xml for initial boot.

    .OUTPUTS
        None.
#>

function New-LabVMInitializationFile
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    # Generate an unattended setup file
    $unattendFile = Get-LabUnattendFileContent `
        -Lab $Lab `
        -VM $VM
    $null = Set-Content `
        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'Unattend.xml') `
        -Value $unattendFile -Force

    # Assemble the SetupComplete.* scripts.
    $setupCompleteCmd = ''

    # Write out the PS1 Setup Complete File
    if ($VM.OSType -eq [LabOSType]::Nano)
    {
        # For a Nano Server we also need to create the certificates
        # to upload to it (because it Nano Server can't generate them)
        $null = New-LabHostSelfSignedCertificate `
            -Lab $Lab `
            -VM $VM

        # PowerShell currently can't find any basic Cmdlets when executed by
        # SetupComplete.cmd during the initialization phase, so create an empty
        # a SetupComplete.ps1
        $setupCompletePs = ''
    }
    else
    {
        if ($VM.CertificateSource -eq [LabCertificateSource]::Host)
        {
            # Generate the PFX certificate on the host
            $null = New-LabHostSelfSignedCertificate `
                -Lab $Lab `
                -VM $VM
        }

        $getCertPs = Get-LabCertificatePsFileContent `
            -Lab $Lab `
            -VM $VM
        $setupCompletePs = @"
Add-Content ``
    -Path "C:\WINDOWS\Setup\Scripts\SetupComplete.log" ``
    -Value 'SetupComplete.ps1 Script Started...' ``
    -Encoding Ascii
Start-Sleep -Seconds 30
$getCertPs
Add-Content ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
    -Value 'Certificate identified and saved to C:\Windows\$script:DSCEncryptionCert ...' ``
    -Encoding Ascii
Enable-PSRemoting -SkipNetworkProfileCheck -Force
Add-Content ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
    -Value 'Windows Remoting Enabled ...' ``
    -Encoding Ascii
"@

    } # if

    if ($VM.SetupComplete)
    {
        $setupComplete = $VM.SetupComplete

        if (-not (Test-Path -Path $setupComplete))
        {
            $exceptionParameters = @{
                errorId = 'SetupCompleteScriptMissingError'
                errorCategory = 'InvalidArgument'
                errorMessage = $($LocalizedData.SetupCompleteScriptMissingError `
                    -f $VM.name,$setupComplete)
            }
            New-LabException @exceptionParameters
        }

        $extension = [System.IO.Path]::GetExtension($setupComplete)

        switch ($extension.ToLower())
        {
            '.ps1'
            {
                $setupCompletePs += Get-Content -Path $setupComplete
                Break
            } # 'ps1'

            '.cmd'
            {
                $setupCompleteCmd += Get-Content -Path $setupComplete
                Break
            } # 'cmd'
        } # Switch
    } # If

    # Write out the CMD Setup Complete File
    if ($VM.OSType -eq [LabOSType]::Nano)
    {
        $setupCompleteCmd = @"
@echo SetupComplete.cmd Script Started... >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log
$setupCompleteCmd
certoc.exe -ImportPFX -p $script:DSCCertificatePassword root $ENV:SystemRoot\$script:DSCEncryptionPfxCert >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log
@echo SetupComplete.cmd Script Finished... >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log
@echo Initial Setup Completed - this file indicates that setup has completed. >> %SYSTEMROOT%\Setup\Scripts\InitialSetupCompleted.txt
"@

    }
    else
    {
        $setupCompleteCmd = @"
@echo SetupComplete.cmd Script Started... >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log`r
$setupCompleteCmd
@echo SetupComplete.cmd Execute SetupComplete.ps1... >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log`r
powerShell.exe -ExecutionPolicy Unrestricted -Command `"%SYSTEMROOT%\Setup\Scripts\SetupComplete.ps1`" `r
@echo SetupComplete.cmd Script Finished... >> %SYSTEMROOT%\Setup\Scripts\SetupComplete.log
@echo Initial Setup Completed - this file indicates that setup has completed. >> %SYSTEMROOT%\Setup\Scripts\InitialSetupCompleted.txt
"@

    }

    $null = Set-Content `
        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'SetupComplete.cmd') `
        -Value $setupCompleteCmd -Force

    # Write out the PowerShell Setup Complete file
    $setupCompletePs = @"
Add-Content ``
    -Path `"$($ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
    -Value 'SetupComplete.ps1 Script Started...' ``
    -Encoding Ascii
$setupCompletePs
Add-Content ``
    -Path `"$($ENV:SystemRoot)\Setup\Scripts\SetupComplete.log`" ``
    -Value 'SetupComplete.ps1 Script Finished...' ``
    -Encoding Ascii
"@

    $null = Set-Content `
        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'SetupComplete.ps1') `
        -Value $setupCompletePs -Force

    # If ODJ file specified copy it to the labuilder path.
    if ($VM.OSType -eq [LabOSType]::Nano `
        -and -not [System.String]::IsNullOrWhiteSpace($VM.NanoODJPath))
    {
        if ([System.IO.Path]::IsPathRooted($VM.NanoODJPath))
        {
            $nanoODJPath = $VM.NanoODJPath
        }
        else
        {
            $nanoODJPath = Join-Path `
                -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                -ChildPath $VM.NanoODJPath
        } # if

        $null = Copy-Item `
            -Path (Join-Path -Path $nanoODJPath -ChildPath "$($VM.ComputerName).txt") `
            -Destination $vmLabBuilderFiles `
            -ErrorAction Stop
    } # if

    Write-LabMessage -Message $($LocalizedData.CreatedVMInitializationFiles `
        -f $VM.Name)
}
#EndRegion './Private/New-LabVMInitializationFile.ps1' 201
#Region './Private/Recieve-LabSelfSignedCertificate.ps1' 0
<#
    .SYNOPSIS
        Download the existing self-signed certificate from a running VM.

    .DESCRIPTION
        This function uses PS Remoting to connect to a running VM and download the an existing
        Self-Signed certificate file that was written to the c:\windows folder of the guest operating
        system by the SetupComplete.ps1 script on the. The certificate will be downloaded to the VM's
        Labbuilder files folder.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .PARAMETER Timeout
        The maximum amount of time that this function can take to download the certificate.
        If the timeout is reached before the process is complete an error will be thrown.
        The timeout defaults to 300 seconds.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Recieve-LabSelfSignedCertificate -Lab $Lab -VM $VMs[0]
        Downloads the existing Self-signed certificate for the VM to the Labbuilder files folder of the
        VM.

    .OUTPUTS
        The path to the certificate file that was downloaded.
#>

function Recieve-LabSelfSignedCertificate
{
    [CmdLetBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter()]
        [System.Int32]
        $Timeout = 300
    )

    $startTime = Get-Date
    $session = $null
    $complete = $false

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    while ((-not $complete) `
        -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
    {
        $session = Connect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        # Failed to connnect to the VM
        if (-not $session)
        {
            $exceptionParameters = @{
                errorId = 'CertificateDownloadError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.CertificateDownloadError `
                    -f $VM.Name)
            }
            New-LabException @exceptionParameters
            return
        } # if

        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and (-not $complete))
        {
            # We connected OK - download the Certificate file
            while ((-not $complete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                try
                {
                    $null = Copy-Item `
                        -Path "c:\windows\$script:DSCEncryptionCert" `
                        -Destination $vmLabBuilderFiles `
                        -FromSession $session `
                        -ErrorAction Stop
                    $complete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.WaitingForCertificateMessage `
                        -f $VM.Name,$script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        # If the copy didn't complete and we're out of time throw an exception
        if ((-not $complete) `
            -and (((Get-Date) - $startTime).TotalSeconds) -ge $TimeOut)
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $exceptionParameters = @{
                errorId = 'CertificateDownloadError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.CertificateDownloadError `
                    -f $VM.Name)
            }
            New-LabException @exceptionParameters
        } # if

        # Close the Session if it is opened and the download is complete
        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and ($complete))
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue
        } # if
    } # while

    return (Get-Item -Path "$vmLabBuilderFiles\$($script:DSCEncryptionCert)")
}
#EndRegion './Private/Recieve-LabSelfSignedCertificate.ps1' 136
#Region './Private/Register-LabPackageSource.ps1' 0
<#
    .SYNOPSIS
        Ensures the Package Sources required by LabBuilder are registered.

    .DESCRIPTION
        This function will check that both the NuGet.org and the PSGallery package
        sources are registered.
        If either of them are missing the function will attempt to register them.

    .EXAMPLE
        Register-LabPackageSource
        Ensures the required Package Sources for LabBuilder are required.

    .OUTPUTS
        None
#>

function Register-LabPackageSource
{
    [CmdLetBinding(SupportsShouldProcess = $true,
        ConfirmImpact = 'High')]
    param
    (
        [Parameter()]
        [Switch]
        $Force
    )

    $requiredPackageSources = @(
        @{
            Name         = 'nuget.org'
            ProviderName = 'NuGet'
            Location     = 'https://www.nuget.org/api/v2/'
        },
        @{
            Name         = 'PSGallery'
            ProviderName = 'PowerShellGet'
            Location     = 'https://www.powershellgallery.com/api/v2/'
        }
    )

    $currentPackageSources = Get-PackageSource -ErrorAction Stop

    foreach ($requiredPackageSource in $requiredPackageSources)
    {
        $packageSource = $currentPackageSources |
            Where-Object -FilterScript {
            $_.Name -eq $requiredPackageSource.Name
        }

        if ($packageSource)
        {
            if (-not $packageSource.IsTrusted)
            {
                if ($Force -or $PSCmdlet.ShouldProcess( 'Localhost', `
                        ($LocalizedData.ShouldTrustPackageSource `
                                -f $requiredPackageSource.Name, $requiredPackageSource.Location )))
                {
                    # The Package source is not trusted so trust it
                    Write-LabMessage -Message ($LocalizedData.RegisterPackageSourceMessage `
                            -f $requiredPackageSource.Name, $requiredPackageSource.Location)

                    $null = Set-PackageSource `
                        -Name $requiredPackageSource.Name `
                        -Trusted `
                        -Force `
                        -ErrorAction Stop
                }
                else
                {
                    # Can't continue if the package source is not trusted.
                    $exceptionParameters = @{
                        errorId       = 'PackageSourceNotTrustedError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.PackageSourceNotTrustedError `
                                -f $requiredPackageSource.Name)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if
        }
        else
        {
            # The Package source is not registered so register it
            if ($Force -or $PSCmdlet.ShouldProcess( 'Localhost', `
                    ($LocalizedData.ShouldRegisterPackageSource `
                            -f $requiredPackageSource.Name, $requiredPackageSource.Location )))
            {
                Write-LabMessage -Message ($LocalizedData.RegisterPackageSourceMessage `
                        -f $requiredPackageSource.Name, $requiredPackageSource.Location)

                $null = Register-PackageSource `
                    -Name $requiredPackageSource.Name `
                    -Location $requiredPackageSource.Location `
                    -ProviderName $requiredPackageSource.ProviderName `
                    -Trusted `
                    -Force `
                    -ErrorAction Stop
            }
            else
            {
                # Can't continue if the package source is not registered.
                $exceptionParameters = @{
                    errorId       = 'PackageSourceNotRegisteredError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.PackageSourceNotRegisteredError `
                            -f $requiredPackageSource.Name)
                }
                New-LabException @exceptionParameters
            } # if
        } # if
    } # foreach
}
#EndRegion './Private/Register-LabPackageSource.ps1' 113
#Region './Private/Request-LabSelfSignedCertificate.ps1' 0
<#
    .SYNOPSIS
        Generate and download a new credential encryption certificate from a running VM.

    .DESCRIPTION
        This function uses PS Remoting to connect to a running VM and upload the GetDSCEncryptionCert.ps1
        script and then run it. This wil create a new self-signed certificate that is written to the
        c:\windows folder of the guest operating system. The certificate will be downloaded to the VM's
        Labbuilder files folder.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .PARAMETER Timeout
        The maximum amount of time that this function can take to download the certificate.
        If the timeout is reached before the process is complete an error will be thrown.
        The timeout defaults to 300 seconds.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Request-LabSelfSignedCertificate -Lab $Lab -VM $VMs[0]
        Causes a new self-signed certificate on the VM and download it to the Labbuilder files folder
        of th VM.

    .OUTPUTS
        The path to the certificate file that was downloaded.
#>

function Request-LabSelfSignedCertificate
{
    [CmdLetBinding()]
    [OutputType([System.IO.FileInfo])]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter()]
        [System.Int32]
        $Timeout = 300
    )

    $startTime = Get-Date
    $session = $null
    $complete = $false

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    # Ensure the certificate generation script has been created
    $getCertPs = Get-LabCertificatePsFileContent `
        -Lab $Lab `
        -VM $VM `
        -CertificateSource Guest

    $null = Set-Content `
        -Path "$VMLabBuilderFiles\GetDSCEncryptionCert.ps1" `
        -Value $getCertPs `
        -Force

    while ((-not $complete) `
        -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
    {
        $session = Connect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        # Failed to connnect to the VM
        if (-not $session)
        {
            $exceptionParameters = @{
                errorId = 'CertificateDownloadError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.CertificateDownloadError `
                    -f $VM.Name)
            }
            New-LabException @exceptionParameters
            return
        } # if

        $complete = $false

        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and (-not $complete))
        {
            # We connected OK - Upload the script
            while ((-not $complete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                try
                {
                    Copy-Item `
                        -Path "$VMLabBuilderFiles\GetDSCEncryptionCert.ps1" `
                        -Destination 'c:\windows\setup\scripts\' `
                        -ToSession $session `
                        -Force `
                        -ErrorAction Stop
                    $complete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.FailedToUploadCertificateCreateScriptMessage `
                        -f $VM.Name,$script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        $complete = $false

        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and (-not $complete))
        {
            # Script uploaded, run it
            while ((-not $complete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                try
                {
                    Invoke-Command -Session $session -ScriptBlock {
                        C:\Windows\Setup\Scripts\GetDSCEncryptionCert.ps1
                    }

                    $complete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.FailedToExecuteCertificateCreateScriptMessage `
                        -f $VM.Name,$script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        $complete = $false

        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and (-not $complete))
        {
            # Now download the Certificate
            while ((-not $complete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                try {
                    $null = Copy-Item `
                        -Path "c:\windows\$($script:DSCEncryptionCert)" `
                        -Destination $vmLabBuilderFiles `
                        -FromSession $session `
                        -ErrorAction Stop

                    $complete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.FailedToDownloadCertificateMessage `
                        -f $VM.Name,$script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # Try
            } # While
        } # If

        # If the process didn't complete and we're out of time throw an exception
        if ((-not $complete) `
            -and (((Get-Date) - $startTime).TotalSeconds) -ge $TimeOut)
        {
            if ($session)
            {
                Remove-PSSession -Session $session
            }

            $exceptionParameters = @{
                errorId = 'CertificateDownloadError'
                errorCategory = 'OperationTimeout'
                errorMessage = $($LocalizedData.CertificateDownloadError `
                    -f $VM.Name)
            }
            New-LabException @exceptionParameters
        }

        # Close the Session if it is opened and the download is complete
        if (($session) `
            -and ($session.State -eq 'Opened') `
            -and ($complete))
        {
            Remove-PSSession -Session $session
        } # If
    } # While

    return (Get-Item -Path "$vmLabBuilderFiles\$($script:DSCEncryptionCert)")
}
#EndRegion './Private/Request-LabSelfSignedCertificate.ps1' 204
#Region './Private/Set-LabDSC.ps1' 0
<#
    .SYNOPSIS
        This function prepares the PowerShell scripts used for starting up DSC on a VM.

    .DESCRIPTION
        Two PowerShell scripts will be created by this function in the LabBuilder Files
        folder of the VM:
            1. StartDSC.ps1 - the script that is called automatically to start up DSC.
            2. StartDSCDebug.ps1 - a debug script that will start up DSC in debug mode.
        These scripts will contain code to perform the following operations:
            1. Configure the names of the Network Adapters so that they will match the
                names in the DSC Configuration files.
            2. Enable/Disable DSC Event Logging.
            3. Apply Configuration to the Local Configuration Manager.
            4. Start DSC.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Set-LabDSC -Lab $Lab -VM $VMs[0]
        Prepare the first VM in the Lab c:\mylab\config.xml for DSC start up.

    .OUTPUTS
        None.
#>

function Set-LabDSC
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    $dscStartPs = ''

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    <#
        Relabel the Network Adapters so that they match what the DSC Networking config will use
        This is because unfortunately the Hyper-V Device Naming feature doesn't work.
    #>

    $managementSwitchName = Get-LabManagementSwitchName -Lab $Lab
    $adapters = [System.String[]] ($VM.Adapters).Name
    $adapters += @($managementSwitchName)

    foreach ($adapter in $adapters)
    {
        $netAdapter = Get-VMNetworkAdapter -VMName $($VM.Name) -Name $adapter

        if (-not $netAdapter)
        {
            $exceptionParameters = @{
                errorId       = 'NetworkAdapterNotFoundError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.NetworkAdapterNotFoundError `
                        -f $adapter, $VM.Name)
            }
            New-LabException @exceptionParameters
        } # if

        $macAddress = $netAdapter.MacAddress

        if (-not $macAddress)
        {
            $exceptionParameters = @{
                errorId       = 'NetworkAdapterBlankMacError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.NetworkAdapterBlankMacError `
                        -f $adapter, $VM.Name)
            }
            New-LabException @exceptionParameters
        } # If

        $dscStartPs += @"
Get-NetAdapter ``
    | Where-Object { `$_.MacAddress.Replace('-','') -eq '$macAddress' } ``
    | Rename-NetAdapter -NewName '$($adapter)'

"@

    } # Foreach

    <#
        Enable DSC logging (as long as it hasn't been already)
        Nano Server doesn't have the Microsoft-Windows-Dsc/Analytic channels so
        Logging can't be enabled.
    #>

    if ($VM.OSType -ne [LabOSType]::Nano)
    {
        $logging = ($VM.DSC.Logging).ToString()

        $dscStartPs += @"
`$Result = & "wevtutil.exe" get-log "Microsoft-Windows-Dsc/Analytic"
if (-not (`$Result -like '*enabled: true*')) {
    & "wevtutil.exe" set-log "Microsoft-Windows-Dsc/Analytic" /q:true /e:$logging
}
`$Result = & "wevtutil.exe" get-log "Microsoft-Windows-Dsc/Debug"
if (-not (`$Result -like '*enabled: true*')) {
    & "wevtutil.exe" set-log "Microsoft-Windows-Dsc/Debug" /q:true /e:$logging
}

"@

    } # if

    # Start the actual DSC Configuration
    $dscStartPs += @"
Set-DscLocalConfigurationManager ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\`" ``
    -Verbose *>> `"`$(`$ENV:SystemRoot)\Setup\Scripts\DSC.log`"
Start-DSCConfiguration ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\`" ``
    -Force ``
    -Verbose *>> `"`$(`$ENV:SystemRoot)\Setup\Scripts\DSC.log`"

"@

    $null = Set-Content `
        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'StartDSC.ps1') `
        -Value $dscStartPs -Force

    $dscStartPsDebug = @"
param (
    [System.Boolean] `$WaitForDebugger
)
Set-DscLocalConfigurationManager ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\`" ``
    -Verbose
if (`$WaitForDebugger)
{
    Enable-DscDebug ``
        -BreakAll
}
Start-DSCConfiguration ``
    -Path `"`$(`$ENV:SystemRoot)\Setup\Scripts\`" ``
    -Force ``
    -Debug ``
    -Wait ``
    -Verbose
if (`$WaitForDebugger)
{
    Disable-DscDebug
}
"@


    $null = Set-Content `
        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'StartDSCDebug.ps1') `
        -Value $dscStartPsDebug -Force
}
#EndRegion './Private/Set-LabDSC.ps1' 159
#Region './Private/Set-LabModulesInDSCConfig.ps1' 0
<#
    .SYNOPSIS
        Sets the Modules Resources that should be imported in a DSC Config.

    .DESCRIPTION
        It will completely replace the list of Imported DSCResources with this new list.

    .PARAMETER DscConfigFile
        Contains the path to the DSC Config file to set resource module names in.

    .PARAMETER DscConfigContent
        Contains the content of the DSC Config to set resource module names in.

    .PARAMETER Modules
        Contains an array of LabDSCModule objects to replace set in the Configuration.

    .EXAMPLE
        Set-LabModulesInDSCConfig -DscConfigFile c:\mydsc\Server01.ps1 -Modules $Modules
        Set the DSC Resource module in the content from file c:\mydsc\server01.ps1

    .EXAMPLE
        Set-LabModulesInDSCConfig -DscConfigContent $DSCConfig -Modules $Modules
        Set the DSC Resource module in the content $DSCConfig

    .OUTPUTS
        A string containing the content of the DSC Config file with the updated
        module names in it.
#>

function Set-LabModulesInDSCConfig
{
    [CmdLetBinding(DefaultParameterSetName = "Content")]
    [OutputType([System.String])]
    param
    (
        [parameter(
            Position = 1,
            ParameterSetName = "Content",
            Mandatory = $true)]
        [System.String]
        $DscConfigContent,

        [parameter(
            Position = 2,
            ParameterSetName = "File",
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $DscConfigFile,

        [parameter(
            Position = 3,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [LabDSCModule[]]
        $Modules
    )

    if ($PSCmdlet.ParameterSetName -eq 'File')
    {
        $DscConfigContent = Get-Content -Path $DscConfigFile -Raw
    } # if

    $regex = "[ \t]*?Import\-DscResource[ \t]+(?:\-ModuleName[ \t]+)?'?`"?([A-Za-z0-9._-]+)`"?'?([ \t]+(-ModuleVersion[ \t]+)?'?`"?([0-9.]+)`"?'?)?[ \t]*[\r\n]+"
    $moduleMatches = [regex]::matches($DscConfigContent, $regex, 'IgnoreCase')

    foreach ($module in $Modules)
    {
        $importCommand = "Import-DscResource -ModuleName '$($module.ModuleName)'"
        if ($module.ModuleVersion)
        {
            $importCommand = "$importCommand -ModuleVersion '$($module.ModuleVersion)'"
        } # if

        $importCommand = " $importCommand`r`n"

        # is this module already in there?
        $found = $false

        foreach ($moduleMatch in $moduleMatches)
        {
            if ($moduleMatch.Groups[1].Value -eq $module.ModuleName)
            {
                # Found the module - so replace it
                $DscConfigContent = ("{0}{1}{2}" -f `
                        $DscConfigContent.Substring(0, $moduleMatch.Index), `
                        $importCommand, `
                        $DscConfigContent.Substring($moduleMatch.Index + $moduleMatch.Length))

                $moduleMatches = [regex]::matches($DscConfigContent, $regex, 'IgnoreCase')
                $found = $true
                break
            } # if
        } # foreach

        if (-not $found)
        {
            if ($moduleMatches.Count -gt 0)
            {
                # Add this to the end of the existing Import-DSCResource lines
                $moduleMatch = $moduleMatches[$moduleMatches.count - 1]
            }
            else
            {
                # There are no existing DSC Resource lines, so add it after
                # Configuration ... { line
                $moduleMatch = [regex]::matches($DscConfigContent, "[ \t]*?Configuration[ \t]+?'?`"?[A-Za-z0-9._-]+`"?'?[ \t]*?[\r\n]*?{[\r\n]*?", 'IgnoreCase')

                if (-not $moduleMatch)
                {
                    $exceptionParameters = @{
                        errorId       = 'DSCConfiguartionMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.DSCConfiguartionMissingError)
                    }
                    New-LabException @exceptionParameters
                }
            } # if

            $DscConfigContent = ("{0}{1}{2}" -f `
                    $DscConfigContent.Substring(0, $moduleMatch.Index + $moduleMatch.Length), `
                    $importCommand, `
                    $DscConfigContent.Substring($moduleMatch.Index + $moduleMatch.Length))

            $moduleMatches = [regex]::matches($DscConfigContent, $regex, 'IgnoreCase')
        } # Module not found so add it to the end
    } # foreach

    return $DscConfigContent
}
#EndRegion './Private/Set-LabModulesInDSCConfig.ps1' 130
#Region './Private/Set-LabSwitchAdapter.ps1' 0
<#
    .SYNOPSIS
        Ensures that the virtual adapter is attached to a Virtual Switch
        and configured correctly.

    .DESCRIPTION
        This function is used to add or update the specified virtual network adapter
        that is used by the Management OS to connect to the specifed virtual switch.

    .PARAMETER Name
        Contains the name of the virtual network adapter to add.

    .PARAMETER SwitchName
        Contains the name of the virtual switch to connect this adapter to.

    .PARAMETER ManagementOS
        Whether or not this adapter is attached to the Management OS.

    .PARAMETER StaticMacAddress
        This optional parameter contains the static MAC address to assign to the virtual
        network adapter.

    .PARAMETER VlanId
        This optional parameter contains the VLan Id to assign to this network adapter.

    .EXAMPLE
        Set-LabSwitchAdapter -Name 'Domain Nat SMB' -SwitchName 'Domain Nat' -VlanId 25

    .OUTPUTS
        None.
#>

function Set-LabSwitchAdapter
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [Parameter(Mandatory = $true)]
        [System.String]
        $SwitchName,

        [Parameter()]
        [Switch]
        $ManagementOS,

        [Parameter()]
        [System.String]
        $StaticMacAddress,

        [Parameter()]
        [AllowNull()]
        [Nullable[System.Int32]]
        $VlanId
    )

    # Determine if we should set the MAC address and VLan Id
    $setVlanId = $PSBoundParameters.ContainsKey('VlanId')
    $setMacAddress = $PSBoundParameters.ContainsKey('StaticMacAddress')

    # Remove VlanId Parameter so this can be splatted
    $null = $PSBoundParameters.Remove('VlanId')
    $null = $PSBoundParameters.Remove('StaticMacAddress')

    $existingManagementAdapter = Get-VMNetworkAdapter `
        @PSBoundParameters `
        -ErrorAction SilentlyContinue

    if (-not $existingManagementAdapter)
    {
        # Adapter does not exist so add it
        if ($SetMacAddress)
        {
            # For a management adapter a Static MAC address can only be assigned at creation time.
            if (-not ([System.String]::IsNullOrEmpty($StaticMacAddress)))
            {
                $PSBoundParameters.Add('StaticMacAddress', $StaticMacAddress)
            }
        }

        $existingManagementAdapter = Add-VMNetworkAdapter `
            @PSBoundParameters `
            -Passthru
    }
    else
    {
        <#
            The MAC Address for an existing Management Adapter can not be changed
            This shouldn't ever happen unless the configuration is changed.
            Not sure of the solution to this problem.
        #>

    }

    # Set or clear the VlanId
    if ($setVlanId)
    {
        $existingManagementAdapterVlan = Get-VMNetworkAdapterVlan `
            -VMNetworkAdapter $existingManagementAdapter

        $existingManagementVlan = $existingManagementAdapterVlan.AccessVlanId

        if ($null -eq $VlanId)
        {
            if ($null -eq $existingManagementVlan)
            {
                $setVMNetworkAdapterVlanParameters = @{
                    VMNetworkAdapter = $existingManagementAdapter
                    Untagged         = $true
                }

                $null = Set-VMNetworkAdapterVlan `
                    @setVMNetworkAdapterVlanParameters `
                    -ErrorAction Stop
            }
        }
        else
        {
            if ($VlanId -ne $existingManagementVlan)
            {
                $setVMNetworkAdapterVlanParameters = @{
                    VMNetworkAdapter = $existingManagementAdapter
                    Access           = $true
                    VlanId           = $VlanId
                }

                $null = Set-VMNetworkAdapterVlan `
                    @setVMNetworkAdapterVlanParameters `
                    -ErrorAction Stop
            }
        }
    }
}
#EndRegion './Private/Set-LabSwitchAdapter.ps1' 135
#Region './Private/Start-LabDSC.ps1' 0
<#
    .SYNOPSIS
        Uploads prepared Modules and MOF files to a VM and starts up Desired State
        Configuration (DSC) on it.

    .DESCRIPTION
        This function will perform the following tasks:
            1. Connect to the VM via remoting.
            2. Upload the DSC and LCM MOF files to the c:\windows\setup\scripts folder of the VM.
            3. Upload DSC Start up scripts to the c:\windows\setup\scripts folder of the VM.
            4. Upload all required modules to the c:\program files\WindowsPowerShell\Modules\ folder
                of the VM.
            5. Invoke the StartDSC.ps1 script on the VM to start DSC processing.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM.

    .PARAMETER Timeout
        The maximum amount of time that this function can take to perform DSC start-up.
        If the timeout is reached before the process is complete an error will be thrown.
        The timeout defaults to 300 seconds.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Start-LabDSC -Lab $Lab -VM $VMs[0]
        Starts up Desired State Configuration for the first VM in the Lab c:\mylab\config.xml.

    .OUTPUTS
        None.
#>

function Start-LabDSC
{
    [CmdLetBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter()]
        [System.Int32]
        $Timeout = 300
    )

    $startTime = Get-Date
    $session = $null
    $complete = $false
    $configCopyComplete = $false
    $moduleCopyComplete = $false

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    While ((-not $complete) `
            -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
    {
        # Connect to the VM
        $session = Connect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        # Failed to connnect to the VM
        if (-not $session)
        {
            $exceptionParameters = @{
                errorId       = 'DSCInitializationError'
                errorCategory = 'OperationTimeout'
                errorMessage  = $($LocalizedData.DSCInitializationError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters

            return
        }

        if (($session) `
                -and ($session.State -eq 'Opened') `
                -and (-not $configCopyComplete))
        {
            $copyParameters = @{
                Destination = 'c:\Windows\Setup\Scripts'
                ToSession   = $session
                Force       = $true
                ErrorAction = 'Stop'
            }

            # Connection has been made OK, upload the DSC files
            While ((-not $configCopyComplete) `
                    -and (((Get-Date) - $startTime).TotalSeconds) -lt $TimeOut)
            {
                Try
                {
                    Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMMessage `
                            -f $VM.Name, 'DSC')

                    $null = Copy-Item `
                        @copyParameters `
                        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath "$($VM.ComputerName).mof")

                    if (Test-Path `
                            -Path "$vmLabBuilderFiles\$($VM.ComputerName).meta.mof")
                    {
                        $null = Copy-Item `
                            @copyParameters `
                            -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath "$($VM.ComputerName).meta.mof")
                    } # If

                    $null = Copy-Item `
                        @copyParameters `
                        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'StartDSC.ps1')

                    $null = Copy-Item `
                        @copyParameters `
                        -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath 'StartDSCDebug.ps1')

                    $configCopyComplete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMFailedMessage `
                            -f $VM.Name, 'DSC', $script:RetryConnectSeconds)

                    Start-Sleep -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        # If the copy didn't complete and we're out of time throw an exception
        if ((-not $configCopyComplete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -ge $TimeOut)
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $exceptionParameters = @{
                errorId       = 'DSCInitializationError'
                errorCategory = 'OperationTimeout'
                errorMessage  = $($LocalizedData.DSCInitializationError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters
        } # if

        # Upload any required modules to the VM
        if (($session) `
                -and ($session.State -eq 'Opened') `
                -and (-not $moduleCopyComplete))
        {
            $dscContent = Get-Content `
                -Path $($VM.DSC.ConfigFile) `
                -Raw
            [LabDSCModule[]] $dscModules = Get-LabModulesInDSCConfig -DSCConfigContent $dscContent

            # Add the NetworkingDsc DSC Resource because it is always used
            $module = [LabDSCModule]::New('NetworkingDsc')
            $dscModules += @( $module )

            foreach ($dscModule in $dscModules)
            {
                $moduleName = $dscModule.ModuleName

                # Upload all but PSDesiredStateConfiguration because it
                # should always exist on client node.
                if ($moduleName -ne 'PSDesiredStateConfiguration')
                {
                    try
                    {
                        Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMMessage `
                                -f $VM.Name, "DSC Module $moduleName")

                        $null = Copy-Item `
                            -Path (Join-Path -Path $vmLabBuilderFiles -ChildPath "DSC Modules\$moduleName\") `
                            -Destination "$($env:ProgramFiles)\WindowsPowerShell\Modules\" `
                            -ToSession $session `
                            -Force `
                            -Recurse `
                            -ErrorAction Stop
                    }
                    catch
                    {
                        Write-LabMessage -Message $($LocalizedData.CopyingFilesToVMFailedMessage `
                                -f $VM.Name, "DSC Module $moduleName", $script:RetryConnectSeconds)

                        Start-Sleep -Seconds $script:RetryConnectSeconds
                    } # try
                } # if
            } # foreach

            $moduleCopyComplete = $true
        } # if

        # If the copy didn't complete and we're out of time throw an exception
        if ((-not $moduleCopyComplete) `
                -and (((Get-Date) - $startTime).TotalSeconds) -ge $TimeOut)
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $exceptionParameters = @{
                errorId       = 'DSCInitializationError'
                errorCategory = 'OperationTimeout'
                errorMessage  = $($LocalizedData.DSCInitializationError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters
        } # if

        # Finally, Start DSC up!
        if (($session) `
                -and ($session.State -eq 'Opened') `
                -and ($configCopyComplete) `
                -and ($moduleCopyComplete))
        {
            Write-LabMessage -Message $($LocalizedData.StartingDSCMessage `
                    -f $VM.Name)

            Invoke-Command -Session $session {
                c:\windows\setup\scripts\StartDSC.ps1
            }

            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $complete = $true
        } # if
    } # while
}
#EndRegion './Private/Start-LabDSC.ps1' 240
#Region './Private/Update-LabDSC.ps1' 0
<#
    .SYNOPSIS
        This function prepares all the files and modules necessary for a VM to be configured using
        Desired State Configuration (DSC).

    .DESCRIPTION
        This funcion performs the following tasks in preparation for starting Desired State
        Configuration on a Virtual Machine:
            1. Ensures the folder structure for the Virtual Machine DSC files is available.
            2. Gets a list of all Modules required by the DSC configuration to be applied.
            3. Download and Install any missing DSC modules required for the DSC configuration.
            4. Copy all modules required for the DSC configuration to the VM folder.
            5. Cause a self-signed cetficiate to be created and downloaded on the Lab VM.
            6. Create a Networking DSC configuration file and ensure the DSC config file calss it.
            7. Create the MOF file from the config and an LCM config.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Update-LabDSC -Lab $Lab -VM $VMs[0]
        Prepare the first VM in the Lab c:\mylab\config.xml for DSC configuration.

    .OUTPUTS
        None.
#>

function Update-LabDSC
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        $Lab,

        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    $dscMOFFile = ''
    $dscMOFMetaFile = ''

    # Get Path to LabBuilder files
    $vmLabBuilderFiles = $VM.LabBuilderFilesPath

    if (-not $VM.DSC.ConfigFile)
    {
        # This VM doesn't have a DSC Configuration
        return
    }

    # Make sure all the modules required to create the MOF file are installed
    $installedModules = Get-Module -ListAvailable

    Write-LabMessage -Message $($LocalizedData.DSCConfigIdentifyModulesMessage `
            -f $VM.DSC.ConfigFile, $VM.Name)

    $dscConfigContent = Get-Content `
        -Path $($VM.DSC.ConfigFile) `
        -Raw

    [LabDSCModule[]] $dscModules = Get-LabModulesInDSCConfig `
        -DSCConfigContent $dscConfigContent

    # Add the NetworkingDsc DSC Resource because it is always used
    $module = [LabDSCModule]::New('NetworkingDsc')

    # It must be 7.0.0.0 or greater
    $module.MinimumVersion = [Version] '7.0.0.0'
    $dscModules += @( $module )

    foreach ($dscModule in $dscModules)
    {
        $moduleName = $dscModule.ModuleName
        $moduleParameters = @{ Name = $ModuleName }
        $moduleVersion = $dscModule.ModuleVersion
        $minimumVersion = $dscModule.MinimumVersion

        if ($moduleVersion)
        {
            $filterScript = {
                ($_.Name -eq $ModuleName) -and ($moduleVersion -eq $_.Version)
            }

            $moduleParameters += @{
                RequiredVersion = $moduleVersion
            }
        }
        elseif ($minimumVersion)
        {
            $filterScript = {
                ($_.Name -eq $ModuleName) -and ($_.Version -ge $minimumVersion)
            }

            $moduleParameters += @{
                MinimumVersion = $minimumVersion
            }
        }
        else
        {
            $filterScript = {
                $_.Name -eq $ModuleName
            }
        }

        $module = ($installedModules |
            Where-Object -FilterScript $filterScript |
            Sort-Object -Property Version -Descending |
            Select-Object -First 1)

        if ($module)
        {
            # The module already exists, load the version number into the Module
            # to force the version number to be set in the DSC Config file
            $dscModule.ModuleVersion = $module.Version
        }
        else
        {
            # The Module isn't available on this computer, so try and install it
            Write-LabMessage -Message $($LocalizedData.DSCConfigSearchingForModuleMessage `
                    -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)

            $newModule = Find-Module `
                @moduleParameters

            if ($newModule)
            {
                Write-LabMessage -Message $($LocalizedData.DSCConfigInstallingModuleMessage `
                        -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)

                try
                {
                    $newModule | Install-Module
                }
                catch
                {
                    $exceptionParameters = @{
                        errorId       = 'DSCModuleDownloadError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.DSCModuleDownloadError `
                                -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)
                    }
                    New-LabException @exceptionParameters
                }
            }
            else
            {
                $exceptionParameters = @{
                    errorId       = 'DSCModuleDownloadError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.DSCModuleDownloadError `
                            -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)
                }
                New-LabException @exceptionParameters
            }

            $dscModule.ModuleVersion = $newModule.Version
        } # if

        Write-LabMessage -Message $($LocalizedData.DSCConfigSavingModuleMessage `
                -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)

        # Find where the module is actually stored
        $modulePath = ''

        foreach ($Path in $ENV:PSModulePath.Split(';'))
        {
            if (-not [System.String]::IsNullOrEmpty($Path))
            {
                $modulePath = Join-Path `
                    -Path $Path `
                    -ChildPath $ModuleName

                if (Test-Path -Path $modulePath)
                {
                    break
                } # If
            }
        } # Foreach

        if (-not (Test-Path -Path $modulePath))
        {
            $exceptionParameters = @{
                errorId       = 'DSCModuleNotFoundError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.DSCModuleNotFoundError `
                        -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName)
            }
            New-LabException @exceptionParameters
        }

        $destinationPath = Join-Path -Path $vmLabBuilderFiles -ChildPath 'DSC Modules\'

        if (-not (Test-Path -Path $destinationPath))
        {
            # Create the DSC Modules folder if it doesn't exist.
            $null = New-Item -Path $destinationPath -ItemType Directory -Force
        } # if

        Write-LabMessage -Message $($LocalizedData.DSCConfigCopyingModuleMessage `
                -f $VM.DSC.ConfigFile, $VM.Name, $ModuleName, $modulePath, $destinationPath)
        Copy-Item `
            -Path $modulePath `
            -Destination $destinationPath `
            -Recurse `
            -Force `
            -ErrorAction Continue
    } # Foreach

    if ($VM.CertificateSource -eq [LabCertificateSource]::Guest)
    {
        # Recreate the certificate if it the source is the Guest
        if (-not (Request-LabSelfSignedCertificate -Lab $Lab -VM $VM))
        {
            $exceptionParameters = @{
                errorId       = 'CertificateCreateError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.CertificateCreateError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters
        }

        # Remove any old self-signed certifcates for this VM
        Get-ChildItem -Path cert:\LocalMachine\My |
            Where-Object { $_.FriendlyName -eq $script:DSCCertificateFriendlyName } |
            Remove-Item
    } # if

    # Add the VM Self-Signed Certificate to the Local Machine store and get the Thumbprint
    $certificateFile = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath $script:DSCEncryptionCert
    $certificate = Import-Certificate `
        -FilePath $certificateFile `
        -CertStoreLocation 'Cert:LocalMachine\My'
    $certificateThumbprint = $certificate.Thumbprint

    # Set the predicted MOF File name
    $dscMOFFile = Join-Path `
        -Path $ENV:Temp `
        -ChildPath "$($VM.ComputerName).mof"
    $dscMOFMetaFile = ([System.IO.Path]::ChangeExtension($dscMOFFile, 'meta.mof'))

    # Generate the LCM MOF File
    Write-LabMessage -Message $($LocalizedData.DSCConfigCreatingLCMMOFMessage -f $dscMOFMetaFile, $VM.Name)

    $null = ConfigLCM `
        -OutputPath $ENV:Temp `
        -ComputerName $($VM.ComputerName) `
        -Thumbprint $certificateThumbprint

    if (-not (Test-Path -Path $dscMOFMetaFile))
    {
        $exceptionParameters = @{
            errorId       = 'DSCConfigMetaMOFCreateError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.DSCConfigMetaMOFCreateError `
                    -f $VM.Name)
        }
        New-LabException @exceptionParameters
    } # If

    # A DSC Config File was provided so create a MOF File out of it.
    Write-LabMessage -Message $($LocalizedData.DSCConfigCreatingMOFMessage -f $VM.DSC.ConfigFile, $VM.Name)

    # Now create the Networking DSC Config file
    $dscNetworkingConfig = Get-LabDSCNetworkingConfig `
        -Lab $Lab -VM $VM
    $NetworkingDscFile = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath 'DSCNetworking.ps1'
    $null = Set-Content `
        -Path $NetworkingDscFile `
        -Value $dscNetworkingConfig
    . $NetworkingDscFile
    $dscFile = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath 'DSC.ps1'

    # Set the Modules List in the DSC Configuration
    $dscConfigContent = Set-LabModulesInDSCConfig `
        -DSCConfigContent $dscConfigContent `
        -Modules $dscModules

    if (-not ($dscConfigContent -match 'Networking Network {}'))
    {
        # Add the Networking Configuration item to the base DSC Config File
        # Find the location of the line containing "Node $AllNodes.NodeName {"
        [System.String] $Regex = '\s*Node\s.*{.*'
        $Matches = [regex]::matches($dscConfigContent, $Regex, 'IgnoreCase')

        if ($Matches.Count -eq 1)
        {
            $dscConfigContent = $dscConfigContent.`
                Insert($Matches[0].Index + $Matches[0].Length, "`r`nNetworking Network {}`r`n")
        }
        else
        {
            $exceptionParameters = @{
                errorId       = 'DSCConfigMoreThanOneNodeError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.DSCConfigMoreThanOneNodeError `
                        -f $VM.DSC.ConfigFile, $VM.Name)
            }
            New-LabException @exceptionParameters
        } # if
    } # if

    # Save the DSC Content
    $null = Set-Content `
        -Path $dscFile `
        -Value $dscConfigContent `
        -Force

    # Hook the Networking DSC File into the main DSC File
    . $dscFile

    $dscConfigName = $VM.DSC.ConfigName

    Write-LabMessage -Message $($LocalizedData.DSCConfigPrepareMessage -f $dscConfigName, $VM.Name)

    # Generate the Configuration Nodes data that always gets passed to the DSC configuration.
    $dscConfigData = @"
@{
    AllNodes = @(
        @{
            NodeName = '$($VM.ComputerName)'
            CertificateFile = '$certificateFile'
            Thumbprint = '$certificateThumbprint'
            LocalAdminPassword = '$($VM.administratorpassword)'
            $($VM.DSC.Parameters)
        }
    )
}
"@

    # Write it to a temp file
    $dscConfigFile = Join-Path `
        -Path $vmLabBuilderFiles `
        -ChildPath 'DSCConfigData.psd1'

    if (Test-Path -Path $dscConfigFile)
    {
        $null = Remove-Item `
            -Path $dscConfigFile `
            -Force
    }

    $null = Set-Content -Path $dscConfigFile -Value $dscConfigData

    # Read the config data into a Hash Table
    $dscConfigData = Import-LocalizedData -BaseDirectory $vmLabBuilderFiles -FileName 'DSCConfigData.psd1'

    # Generate the MOF file from the configuration
    $null = & $dscConfigName `
        -OutputPath $ENV:Temp `
        -ConfigurationData $dscConfigData `
        -ErrorAction Stop

    if (-not (Test-Path -Path $dscMOFFile))
    {
        $exceptionParameters = @{
            errorId       = 'DSCConfigMOFCreateError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.DSCConfigMOFCreateError -f $VM.DSC.ConfigFile, $VM.Name)
        }
        New-LabException @exceptionParameters
    } # If

    # Remove the VM Self-Signed Certificate from the Local Machine Store
    $null = Remove-Item `
        -Path "Cert:LocalMachine\My\$certificateThumbprint" `
        -Force

    Write-LabMessage -Message $($LocalizedData.DSCConfigMOFCreatedMessage -f $VM.DSC.ConfigFile, $VM.Name)

    # Copy the files to the LabBuilder Files folder
    $dscMOFDestinationFile = Join-Path -Path $vmLabBuilderFiles -ChildPath "$($VM.ComputerName).mof"
    $null = Copy-Item `
        -Path $dscMOFFile `
        -Destination $dscMOFDestinationFile `
        -Force

    if (-not $VM.DSC.MOFFile)
    {
        # Remove Temporary files created by DSC
        $null = Remove-Item `
            -Path $dscMOFFile `
            -Force
    }

    if (Test-Path -Path $dscMOFMetaFile)
    {
        $dscMOFMetaDestinationFile = Join-Path -Path $vmLabBuilderFiles -ChildPath "$($VM.ComputerName).meta.mof"
        $null = Copy-Item `
            -Path $dscMOFMetaFile `
            -Destination $dscMOFMetaDestinationFile `
            -Force

        if (-not $VM.DSC.MOFFile)
        {
            # Remove Temporary files created by DSC
            $null = Remove-Item `
                -Path $dscMOFMetaFile `
                -Force
        }
    } # if
}
#EndRegion './Private/Update-LabDSC.ps1' 414
#Region './Private/Update-LabVMDataDisk.ps1' 0
<#
    .SYNOPSIS
        Updates the VM Data Disks to match the VM Configuration.

    .DESCRIPTION
        This cmdlet will take the VM configuration provided and ensure that that data disks that are
        attached to the VM.

        The function will use the array of items in the DataVHDs property of the VM to create and
        attach any data disk VHDs that are missing.

        If the data disk VHD file exists but is not attached it will be attached to the VM. If the
        data disk VHD file does not exist then it will be created and attached.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Update-LabVMDataDisk -Lab $Lab -VM VM[0]
        This will update the data disks for the first VM in the configuration file c:\mylab\config.xml.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .OUTPUTS
        None.
#>

function Update-LabVMDataDisk
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Mandatory,
            Position=0)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Mandatory,
            Position=1)]
        [ValidateNotNullOrEmpty()]
        [LabVM]
        $VM
    )

    # If there are no data VHDs just return
    if (-not $VM.DataVHDs)
    {
        return
    }

    # Get the root path of the VM
    $vmRootPath = $VM.VMRootPath

    # Get the Virtual Hard Disk Path
    $vhdPath = Join-Path `
        -Path $vmRootPath `
        -ChildPath 'Virtual Hard Disks'

    foreach ($dataVhd in @($VM.DataVHDs))
    {
        $vhd = $dataVhd.Vhd
        if (Test-Path -Path $vhd)
        {
            Write-LabMessage -Message $($LocalizedData.VMDiskAlreadyExistsMessage `
                -f $VM.Name,$vhd,'Data')

            # Check the parameters of the VHD match
            $existingVhd = Get-VHD -Path $vhd

            # Check the VHD Type
            if (($dataVhd.VhdType) `
                -and ($existingVhd.VhdType.ToString() -ne $dataVhd.VhdType.ToString()))
            {
                # The type of disk can't be changed.
                $exceptionParameters = @{
                    errorId = 'VMDataDiskVHDConvertError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.VMDataDiskVHDConvertError `
                        -f $VM.name,$vhd,$dataVhd.VhdType)
                }
                New-LabException @exceptionParameters
            }

            # Check the size
            if ($dataVhd.Size)
            {
                if ($existingVhd.Size -lt $dataVhd.Size)
                {
                    # Expand the disk
                    Write-LabMessage -Message $($LocalizedData.ExpandingVMDiskMessage `
                        -f $VM.Name,$vhd,'Data',$dataVhd.Size)

                    $null = Resize-VHD `
                        -Path $vhd `
                        -SizeBytes $dataVhd.Size
                }
                elseif ($existingVhd.Size -gt $dataVhd.Size)
                {
                    <#
                        The disk size can't be reduced.
                        This could be revisited later.
                    #>

                    $exceptionParameters = @{
                        errorId = 'VMDataDiskVHDShrinkError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDataDiskVHDShrinkError `
                            -f $VM.name,$vhd,$dataVhd.Size)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if
        }
        else
        {
            # The data disk VHD does not exist so create it
            $SourceVhd = $dataVhd.SourceVhd
            if ($SourceVhd)
            {
                # A source VHD was specified to create the new VHD using
                if (! (Test-Path -Path $SourceVhd))
                {
                    $exceptionParameters = @{
                        errorId = 'VMDataDiskSourceVHDNotFoundError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDataDiskSourceVHDNotFoundError `
                            -f $VM.name,$SourceVhd)
                    }
                    New-LabException @exceptionParameters
                } # if

                # Should the Source VHD be copied or moved
                if ($dataVhd.MoveSourceVHD)
                {
                    Write-LabMessage -Message $($LocalizedData.CreatingVMDiskByMovingSourceVHDMessage `
                        -f $VM.Name,$vhd,$SourceVhd)

                    $null = Move-Item `
                        -Path $SourceVhd `
                        -Destination $vhdPath `
                        -Force `
                        -ErrorAction Stop
                }
                else
                {
                    Write-LabMessage -Message $($LocalizedData.CreatingVMDiskByCopyingSourceVHDMessage `
                        -f $VM.Name,$vhd,$SourceVhd)

                    $null = Copy-Item `
                        -Path $SourceVhd `
                        -Destination $vhdPath `
                        -Force `
                        -ErrorAction Stop
                } # if
            }
            else
            {
                $size = $dataVhd.size

                switch ($dataVhd.VhdType)
                {
                    'fixed'
                    {
                        # Create a new Fixed VHD
                        Write-LabMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                            -f $VM.Name,$vhd,'Fixed Data')

                        $null = New-VHD `
                            -Path $vhd `
                            -SizeBytes $size `
                            -Fixed `
                            -ErrorAction Stop
                        break;
                    } # 'fixed'

                    'dynamic'
                    {
                        # Create a new Dynamic VHD
                        Write-LabMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                            -f $VM.Name,$vhd,'Dynamic Data')

                        $null = New-VHD `
                            -Path $vhd `
                            -SizeBytes $size `
                            -Dynamic `
                            -ErrorAction Stop
                        break;
                    } # 'dynamic'

                    'differencing'
                    {
                        <#
                            A differencing disk is specified so check the Parent VHD
                            is specified and exists.
                        #>

                        $ParentVhd = $dataVhd.ParentVhd
                        if (-not $ParentVhd)
                        {
                            $exceptionParameters = @{
                                errorId = 'VMDataDiskParentVHDMissingError'
                                errorCategory = 'InvalidArgument'
                                errorMessage = $($LocalizedData.VMDataDiskParentVHDMissingError `
                                    -f $VM.name)
                            }
                            New-LabException @exceptionParameters
                        } # if
                        if (-not (Test-Path -Path $ParentVhd))
                        {
                            $exceptionParameters = @{
                                errorId = 'VMDataDiskParentVHDNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage = $($LocalizedData.VMDataDiskParentVHDNotFoundError `
                                    -f $VM.name,$ParentVhd)
                            }
                            New-LabException @exceptionParameters
                        } # if

                        # Create a new Differencing VHD
                        Write-LabMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                            -f $VM.Name,$vhd,"Differencing Data using Parent '$ParentVhd'")

                        $null = New-VHD `
                            -Path $vhd `
                            -SizeBytes $size `
                            -Differencing `
                            -ParentPath $ParentVhd `
                            -ErrorAction Stop
                        break;
                    } # 'differencing'

                    default
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskUnknownTypeError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskUnknownTypeError `
                                -f $VM.Name,$vhd,$dataVhd.VhdType)
                        }
                        New-LabException @exceptionParameters
                    } # default
                } # switch
            } # if

            # Do folders need to be copied to this Data Disk?
            if ($null -ne $dataVhd.CopyFolders)
            {
                <#
                    Files need to be copied to this Data VHD so
                    set up a mount folder for it to be mounted to.
                    Get Path to LabBuilder files
                #>

                $vmLabBuilderFiles = $VM.LabBuilderFilesPath

                $mountPoint = Join-Path `
                    -Path $vmLabBuilderFiles `
                    -ChildPath 'VHDMount'

                if (-not (Test-Path -Path $mountPoint -PathType Container))
                {
                    $null = New-Item `
                        -Path $mountPoint `
                        -ItemType Directory
                }

                # Yes, initialize the disk (or check it is)
                $initializeLabVHDParams = @{
                    Path = $vhd
                    AccessPath = $mountPoint
                }

                # Are we allowed to initialize/format the disk?
                if ($dataVhd.PartitionStyle -and $dataVhd.FileSystem)
                {
                    # Yes, initialize the disk
                    $initializeLabVHDParams += @{
                        PartitionStyle = $dataVhd.PartitionStyle
                        FileSystem = $dataVhd.FileSystem
                    }

                    # Set a FileSystemLabel too?
                    if ($dataVhd.FileSystemLabel)
                    {
                        $initializeLabVHDParams += @{
                            FileSystemLabel = $dataVhd.FileSystemLabel
                        }
                    }
                }

                Write-LabMessage -Message $($LocalizedData.InitializingVMDiskMessage `
                    -f $VM.Name,$vhd)

                Initialize-LabVHD `
                    @initializeLabVHDParams `
                    -ErrorAction Stop

                # Copy each folder to the VM Data Disk
                foreach ($copyFolder in @($dataVhd.CopyFolders))
                {
                    Write-LabMessage -Message $($LocalizedData.CopyingFoldersToVMDiskMessage `
                        -f $VM.Name,$vhd,$copyFolder)

                    Copy-item `
                        -Path $copyFolder `
                        -Destination $mountPoint `
                        -Recurse `
                        -Force
                }

                # Dismount the VM Data Disk
                Write-LabMessage -Message $($LocalizedData.DismountingVMDiskMessage `
                    -f $VM.Name,$vhd)

                Dismount-VHD `
                    -Path $vhd `
                    -ErrorAction Stop
            }
            else
            {
                <#
                    No folders need to be copied but check if we
                    need to initialize the new disk.
                #>

                if ($dataVhd.PartitionStyle -and $dataVhd.FileSystem)
                {
                    $InitializeVHDParams = @{
                        Path = $vhd
                        PartitionStyle = $dataVhd.PartitionStyle
                        FileSystem = $dataVhd.FileSystem
                    }

                    if ($dataVhd.FileSystemLabel)
                    {
                        $InitializeVHDParams += @{
                            FileSystemLabel = $dataVhd.FileSystemLabel
                        }
                    } # if

                    Write-LabMessage -Message $($LocalizedData.InitializingVMDiskMessage `
                        -f $VM.Name,$vhd)

                    Initialize-LabVHD `
                        @InitializeVHDParams `
                        -ErrorAction Stop

                    # Dismount the VM Data Disk
                    Write-LabMessage -Message $($LocalizedData.DismountingVMDiskMessage `
                        -f $VM.Name,$vhd)

                    Dismount-VHD `
                        -Path $vhd `
                        -ErrorAction Stop
                } # if
            } # if
        } # if

        # Get a list of disks attached to the VM
        $VMHardDiskDrives = Get-VMHardDiskDrive `
            -VMName $VM.Name

        # The data disk VHD will now exist so ensure it is attached
        if (($VMHardDiskDrives | Where-Object -Property Path -eq $vhd).Count -eq 0)
        {
            # The data disk is not yet attached
            Write-LabMessage -Message $($LocalizedData.AddingVMDiskMessage `
                -f $VM.Name,$vhd,'Data')

            <#
                Determine the ControllerLocation and ControllerNumber to
                attach the VHD to.
            #>

            $controllerLocation = ($VMHardDiskDrives |
                Measure-Object -Property ControllerLocation -Maximum).Maximum + 1

            $newHardDiskParams = @{
                VMName = $VM.Name
                Path = $vhd
                ControllerType = 'SCSI'
                ControllerLocation = $controllerLocation
                ControllerNumber = 0
                ErrorAction = 'Stop'
            }
            if ($dataVhd.Shared -or $dataVhd.SupportPR)
            {
                $newHardDiskParams += @{
                    SupportPersistentReservations = $true
                }
            } # if

            Write-Verbose -Message ($newHardDiskParams | Out-String | Fl *) -Verbose

            $null = Add-VMHardDiskDrive @newHardDiskParams
        } # if
    } # foreach
}
#EndRegion './Private/Update-LabVMDataDisk.ps1' 398
#Region './Private/Update-LabVMDvdDrive.ps1' 0
<#
    .SYNOPSIS
        Updates the VM DVD Drives to match the VM Configuration.

    .DESCRIPTION
        This cmdlet will take the VM configuration provided and ensure that the DVD Drives are
        attached to the VM and with the specified ISO.

        The function will use the array of items in the DVDDrives property of the VM to create and
        attach any DVD Drives that are missing.

        If an ISO File is specified in the DVD Drive then it will be mounted to the DVD Drive.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Update-LabVMDvdDrive -Lab $Lab -VM VM[0]
        This will update the DVD Drives for the first VM in the configuration file c:\mylab\config.xml.

    .PARAMETER Lab
        Contains the Lab object that was produced by the Get-Lab cmdlet.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .OUTPUTS
        None.
#>

function Update-LabVMDvdDrive
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Mandatory,
            Position=0)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Mandatory,
            Position=1)]
        [ValidateNotNullOrEmpty()]
        [LabVM]
        $VM
    )

    # If there are no DVD Drives just return
    if (-not $VM.DVDDrives)
    {
        return
    }

    [System.Int32] $DVDDriveCount = 0
    foreach ($DVDDrive in @($VM.DVDDrives))
    {
        # Get a list of DVD Drives attached to the VM
        $VMDVDDrives = @(Get-VMDVDDrive `
            -VMName $VM.Name)

        # The DVD Drive will now exist so ensure it is attached
        if ($VMDVDDrives[$DVDDriveCount])
        {
            # The DVD Drive is already attached then make sure the correct ISO
            if ($VMDVDDrives[$DVDDriveCount].Path -ne $DVDDrive.Path)
            {
                if ($DVDDrive.Path)
                {
                    Write-LabMessage -Message $($LocalizedData.MountingVMDVDDriveISOMessage `
                        -f $VM.Name,$DVDDrive.Path)
                }
                else
                {
                    Write-LabMessage -Message $($LocalizedData.DismountingVMDVDDriveISOMessage `
                        -f $VM.Name,$VMDVDDrives[$DVDDriveCount].Path)
                } # if
                Set-VMDVDDrive `
                    -VMName $VM.Name `
                    -ControllerNumber $VMDVDDrives[$DVDDriveCount].ControllerNumber `
                    -ControllerLocation $VMDVDDrives[$DVDDriveCount].ControllerLocation `
                    -Path $DVDDrive.Path
            } # if
        }
        else
        {
            # The DVD Drive does not exist
            Write-LabMessage -Message $($LocalizedData.AddingVMDVDDriveMessage `
                -f $VM.Name)

            $NewDVDDriveParams = @{
                VMName = $VM.Name
                ErrorAction = 'Stop'
            }

            if ($DVDDrive.Path)
            {
                Write-LabMessage -Message $($LocalizedData.MountingVMDVDDriveISOMessage `
                    -f $VM.Name,$DVDDrive.Path)

                $NewDVDDriveParams += @{
                    Path = $DVDDrive.Path
                }
            } # if
            $null = Add-VMDVDDrive @NewDVDDriveParams
        } # if
        $DVDDriveCount++
    } # foreach
}
#EndRegion './Private/Update-LabVMDvdDrive.ps1' 109
#Region './Private/Update-LabVMIntegrationService.ps1' 0
<#
    .SYNOPSIS
        Updates the VM Integration Services to match the VM Configuration.

    .DESCRIPTION
        This cmdlet will take the VM object provided and ensure the integration services specified
        in it are enabled.

        The function will use comma delimited list of integration services in the VM object passed
        and enable the integration services listed for this VM.

        If the IntegrationServices property of the VM is not set or set to null then ALL integration
        services will be ENABLED.

        If the IntegrationServices property of the VM is set but is blank then ALL integration
        services will be DISABLED.

        The IntegrationServices property should contain a comma delimited list of Integration Services
        that should be enabled.

        The currently available Integration Services are:
        - Guest Service Interface
        - Heartbeat
        - Key-Value Pair Exchange
        - Shutdown
        - Time Synchronization
        - VSS

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Update-LabVMIntegrationService -VM VM[0]
        This will update the Integration Services for the first VM in the configuration file c:\mylab\config.xml.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .OUTPUTS
        None.
#>

function Update-LabVMIntegrationService
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Mandatory,
            Position=1)]
        [ValidateNotNullOrEmpty()]
        [LabVM]
        $VM
    )

    # Configure the Integration services
    $integrationServices = $VM.IntegrationServices
    if ($null -eq $integrationServices)
    {
        # Get the full list of Integration Service names localized
        $integrationServices = ((Get-LabIntegrationServiceName) -Join ',')
    }

    $enabledIntegrationServices = $integrationServices -split ','
    $existingIntegrationServices = Get-VMIntegrationService `
        -VMName $VM.Name `
        -ErrorAction Stop

    # Loop through listed integration services and enable them
    foreach ($existingIntegrationService in $existingIntegrationServices)
    {
        if ($existingIntegrationService.Name -in $enabledIntegrationServices)
        {
            # This integration service should be enabled
            if (-not $existingIntegrationService.Enabled)
            {
                # It is disabled so enable it
                $existingIntegrationService | Enable-VMIntegrationService

                Write-LabMessage -Message $($LocalizedData.EnableVMIntegrationServiceMessage `
                    -f $VM.Name,$existingIntegrationService.Name)
            } # if
        }
        else
        {
            # This integration service should be disabled
            if ($existingIntegrationService.Enabled)
            {
                # It is enabled so disable it
                $existingIntegrationService | Disable-VMIntegrationService

                Write-LabMessage -Message $($LocalizedData.DisableVMIntegrationServiceMessage `
                    -f $VM.Name,$existingIntegrationService.Name)
            } # if
        } # if
    } # foreach
}
#EndRegion './Private/Update-LabVMIntegrationService.ps1' 96
#Region './Private/Wait-LabVMInitializationComplete.ps1' 0
<#
    .SYNOPSIS
        Waits for a VM to complete setup.

    .DESCRIPTION
        When a VM starts up for the first time various scripts are run that prepare the Virtual Machine
        to be managed as part of a Lab. This function will wait for these scripts to complete.
        It determines if the setup has been completed by using PowerShell remoting to connect to the
        VM and downloading the c:\windows\Setup\Scripts\InitialSetupCompleted.txt file. If this file
        does not exist then the initial setup has not been completed.

        The cmdlet will wait for a maximum of 300 seconds for this process to be completed.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .PARAMETER Timeout
        The maximum amount of time that this function will wait for the setup to complete.
        If the timeout is reached before the process is complete an error will be thrown.
        The timeout defaults to 300 seconds.

    .EXAMPLE
        $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
        $VMs = Get-LabVM -Lab $Lab
        Wait-LabVMInitializationComplete -VM $VMs[0]
        Waits for the initial setup to complete on the first VM in the config.xml.

    .OUTPUTS
        The path to the local copy of the Initial Setup complete file in the Labbuilder files folder
        for this VM.
#>

function Wait-LabVMInitializationComplete
{
    [CmdLetBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter()]
        [System.Int32]
        $Timeout = 300
    )

    [DateTime] $StartTime = Get-Date
    [System.Management.Automation.Runspaces.PSSession] $Session = $null
    [System.Boolean] $Complete = $false

    # Get the root path of the VM
    [System.String] $VMRootPath = $VM.VMRootPath

    # Get Path to LabBuilder files
    [System.String] $VMLabBuilderFiles = $VM.LabBuilderFilesPath

    # Make sure the VM has started
    Wait-LabVMStarted -VM $VM

    [System.String] $InitialSetupCompletePath = Join-Path `
        -Path $VMLabBuilderFiles `
        -ChildPath 'InitialSetupCompleted.txt'

    # Check the initial setup on this VM hasn't already completed
    if (Test-Path -Path $InitialSetupCompletePath)
    {
        Write-LabMessage -Message $($LocalizedData.InitialSetupIsAlreadyCompleteMessaage `
                -f $VM.Name)
        return $InitialSetupCompletePath
    }

    while ((-not $Complete) `
            -and (((Get-Date) - $StartTime).TotalSeconds) -lt $TimeOut)
    {
        # Connect to the VM
        $Session = Connect-LabVM `
            -VM $VM `
            -ErrorAction Continue

        # Failed to connnect to the VM
        if (-not $Session)
        {
            $exceptionParameters = @{
                errorId       = 'InitialSetupCompleteError'
                errorCategory = 'OperationTimeout'
                errorMessage  = $($LocalizedData.InitialSetupCompleteError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters
            return
        }

        if (($Session) `
                -and ($Session.State -eq 'Opened') `
                -and (-not $Complete))
        {
            # We connected OK - Download the script
            while ((-not $Complete) `
                    -and (((Get-Date) - $StartTime).TotalSeconds) -lt $TimeOut)
            {
                try
                {
                    $null = Copy-Item `
                        -Path "c:\windows\Setup\Scripts\InitialSetupCompleted.txt" `
                        -Destination $VMLabBuilderFiles `
                        -FromSession $Session `
                        -Force `
                        -ErrorAction Stop
                    $Complete = $true
                }
                catch
                {
                    Write-LabMessage -Message $($LocalizedData.WaitingForInitialSetupCompleteMessage `
                            -f $VM.Name, $script:RetryConnectSeconds)
                    Start-Sleep `
                        -Seconds $script:RetryConnectSeconds
                } # try
            } # while
        } # if

        # If the process didn't complete and we're out of time throw an exception
        if ((-not $Complete) `
                -and (((Get-Date) - $StartTime).TotalSeconds) -ge $TimeOut)
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue

            $exceptionParameters = @{
                errorId       = 'InitialSetupCompleteError'
                errorCategory = 'OperationTimeout'
                errorMessage  = $($LocalizedData.InitialSetupCompleteError `
                        -f $VM.Name)
            }
            New-LabException @exceptionParameters
        }

        # Close the Session if it is opened
        if (($Session) `
                -and ($Session.State -eq 'Opened'))
        {
            # Disconnect from the VM
            Disconnect-LabVM `
                -VM $VM `
                -ErrorAction Continue
        } # if
    } # while

    return $InitialSetupCompletePath
}
#EndRegion './Private/Wait-LabVMInitializationComplete.ps1' 152
#Region './Private/Wait-LabVMOff.ps1' 0
<#
    .SYNOPSIS
        Wait for VM to enter the Off state.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM.

    .OUTPUTS
        None.
#>

function Wait-LabVMOff
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    $runningVM = Get-VM -Name $VM.Name
    while ($runningVM.State -ne 'Off')
    {
        $runningVM = Get-VM -Name $VM.Name
        Start-Sleep -Seconds $script:RetryHeartbeatSeconds
    } # while
}
#EndRegion './Private/Wait-LabVMOff.ps1' 28
#Region './Private/Wait-LabVMStarted.ps1' 0
<#
    .SYNOPSIS
        Wait for the VM to enter the running state.

    .PARAMETER VM
        A LabVM object pulled from the Lab Configuration file using Get-LabVM

    .OUTPUTS
        None.
#>

function Wait-LabVMStarted
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [LabVM]
        $VM
    )

    # If the VM is not running then throw an exception
    if ((Get-VM -VMName $VM.Name).State -ne 'Running') {
        $exceptionParameters = @{
            errorId = 'VMNotRunningHeartbeatMessage'
            errorCategory = 'InvalidArgument'
            errorMessage = $($LocalizedData.VMNotRunningHeartbeatMessage `
                -f $VM.name)
        }
        New-LabException @exceptionParameters
    } # if

    # Names of IntegrationServices are not culture neutral, but have an ID
    $heartbeatCultureNeutral = ( Get-VMIntegrationService -VMName $VM.Name | Where-Object { $_.ID -match "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47" } ).Name
    $heartbeat = Get-VMIntegrationService -VMName $VM.Name -Name $heartbeatCultureNeutral

    while (($heartbeat.PrimaryStatusDescription -ne 'OK') -and (-not [System.String]::IsNullOrEmpty($heartbeat.PrimaryStatusDescription)))
    {
        $heartbeat = Get-VMIntegrationService -VMName $VM.Name -Name $heartbeatCultureNeutral

        Write-LabMessage -Message $($LocalizedData.WaitingForVMHeartbeatMessage `
            -f $VM.Name,$script:RetryHeartbeatSeconds)

        Start-Sleep -Seconds $script:RetryHeartbeatSeconds
    } # while
}
#EndRegion './Private/Wait-LabVMStarted.ps1' 46
#Region './Private/Write-LabMessage.ps1' 0
<#
    .SYNOPSIS
        Writes a Message of the specified Type.

    .DESCRIPTION
        This cmdlet will write a message along with the time to the specified output stream.

    .PARAMETER Type
        This can be one of the following:
        Error - Writes to the Error Stream.
        Warning - Writes to the Warning Stream.
        Verbose - Writes to the Verbose Stream (default)
        Debug - Writes to the Debug Stream.
        Information - Writes to the Information Stream.
        Output - Writes to the Output Stream (so should be used for a terminating message)

    .PARAMETER Message
        The Message to output.

    .PARAMETER ForegroundColor
        The foreground color of the message if being writen to the output stream.

    .EXAMPLE
        Write-LabMessage -Type Verbose -Message 'Downloading file'
        New-LabException @exceptionParameters
        Outputs the message 'Downloading file' to the Verbose stream.

    .OUTPUTS
        None
#>

function Write-LabMessage
{
    [CmdLetBinding()]
    param
    (
        [Parameter()]
        [ValidateSet('Error', 'Warning', 'Verbose', 'Debug', 'Info', 'Alert')]
        [System.String]
        $Type = 'Verbose',

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message,

        [Parameter()]
        [System.String]
        $ForegroundColor = 'Yellow'
    )

    $time = Get-Date -UFormat %T

    switch ($Type)
    {
        'Error'
        {
            Write-Error -Message $Message
            break
        }

        'Warning'
        {
            Write-Warning -Message ('[{0}]: {1}' -f $time, $Message)
            break
        }

        'Verbose'
        {
            Write-Verbose -Message ('[{0}]: {1}' -f $time, $Message)
            break
        }

        'Debug'
        {
            Write-Debug -Message ('[{0}]: {1}' -f $time, $Message)
            break
        }

        'Info'
        {
            Write-Information -MessageData ('INFO: [{0}]: {1}' -f $time, $Message)
            break
        }

        'Alert'
        {
            Write-Host `
                -ForegroundColor $ForegroundColor `
                -Object $Message
            break
        }
    } # switch
}
#EndRegion './Private/Write-LabMessage.ps1' 94
#Region './Public/Connect-LabVm.ps1' 0
function Connect-LabVM
{
    [OutputType([System.Management.Automation.Runspaces.PSSession])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [LabVM]
        $VM,

        [Parameter(
            Position = 2)]
        [System.Int32]
        $ConnectTimeout = 300
    )

    $startTime = Get-Date
    $session = $null
    $adminCredential = New-LabCredential `
        -Username '.\Administrator' `
        -Password $VM.AdministratorPassword
    $fatalException = $false

    while (($null -eq $session) `
            -and (((Get-Date) - $startTime).TotalSeconds) -lt $ConnectTimeout `
            -and -not $fatalException)
    {
        try
        {
            <#
                Get the Management IP Address of the VM
                We repeat this because the IP Address will only be assiged
                once the VM is fully booted.
            #>

            $ipAddress = Get-LabVMManagementIPAddress `
                -Lab $Lab `
                -VM $VM

            <#
                Add the IP Address to trusted hosts if not already in it
                This could be avoided if able to use SSL or if PS Direct is used.
                Also, don't add if TrustedHosts is already *
            #>

            $trustedHosts = (Get-Item -Path WSMAN::localhost\Client\TrustedHosts).Value

            if (($trustedHosts -notlike "*$ipAddress*") -and ($trustedHosts -ne '*'))
            {
                if ([System.String]::IsNullOrWhitespace($trustedHosts))
                {
                    $trustedHosts = "$ipAddress"
                }
                else
                {
                    $trustedHosts = "$trustedHosts,$ipAddress"
                }

                Set-Item `
                    -Path WSMAN::localhost\Client\TrustedHosts `
                    -Value $trustedHosts `
                    -Force
                Write-LabMessage -Message $($LocalizedData.AddingIPAddressToTrustedHostsMessage `
                        -f $VM.Name, $ipAddress)
            }

            if (Test-WSMan -ComputerName $ipAddress -ErrorAction SilentlyContinue)
            {
                Write-LabMessage -Message $($LocalizedData.ConnectingVMMessage `
                        -f $VM.Name, $ipAddress)

                $session = New-PSSession `
                    -Name 'LabBuilder' `
                    -ComputerName $ipAddress `
                    -Credential $adminCredential `
                    -ErrorAction Stop
            }
            else
            {
                Write-LabMessage -Message $($LocalizedData.WaitingForIPAddressAssignedMessage `
                        -f $VM.Name, $script:RetryConnectSeconds)
            }
        }
        catch
        {
            if (-not $ipAddress)
            {
                Write-LabMessage -Message $($LocalizedData.WaitingForIPAddressAssignedMessage `
                        -f $VM.Name, $script:RetryConnectSeconds)
            }
            else
            {
                Write-LabMessage -Message $($LocalizedData.ConnectingVMFailedMessage `
                        -f $VM.Name, $script:RetryConnectSeconds, $_.Exception.Message)
            }

            Start-Sleep -Seconds $script:RetryConnectSeconds
        } # Try
    } # While

    <#
        If a fatal exception occured or the connection just couldn't be established
        then throw an exception so it can be caught by the calling code.
    #>

    if ($fatalException -or ($null -eq $session))
    {
        # The connection failed so throw an error
        $exceptionParameters = @{
            errorId       = 'RemotingConnectionError'
            errorCategory = 'ConnectionError'
            errorMessage  = $($LocalizedData.RemotingConnectionError `
                    -f $VM.Name)
        }
        New-LabException @exceptionParameters
    }

    return $session
} # Connect-LabVM
#EndRegion './Public/Connect-LabVm.ps1' 119
#Region './Public/Disconnect-LabVm.ps1' 0
function Disconnect-LabVM
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [LabVM] $VM
    )

    $adminCredential = New-LabCredential `
        -Username '.\Administrator' `
        -Password $VM.AdministratorPassword

    # Get the Management IP Address of the VM
    $ipAddress = Get-LabVMManagementIPAddress `
        -Lab $Lab `
        -VM $VM

    try
    {
        # Look for the session
        $session = Get-PSSession `
            -Name 'LabBuilder' `
            -ComputerName $ipAddress `
            -Credential $adminCredential `
            -ErrorAction Stop

        if (-not $session)
        {
            # No session found to this machine so nothing to do.
            Write-LabMessage -Message $($LocalizedData.VMSessionDoesNotExistMessage `
                -f $VM.Name)
        }
        else
        {
            if ($session.State -eq 'Opened')
            {
                # Disconnect the session
                $null = $session | Disconnect-PSSession
                Write-LabMessage -Message $($LocalizedData.DisconnectingVMMessage `
                    -f $VM.Name,$IPAddress)
            }
            # Remove the session
            $null = $session | Remove-PSSession -ErrorAction SilentlyContinue
        }
    }
    catch
    {
        Throw $_
    }
    finally
    {
        # Remove the entry from TrustedHosts
        $trustedHosts = (Get-Item -Path WSMAN::localhost\Client\TrustedHosts).Value

        if (($trustedHosts -like "*$ipAddress*") -and ($trustedHosts -ne '*'))
        {
            $ipAddresses = @($trustedHosts -split ',')
            $trustedHosts = ($ipAddresses | Where-Object -FilterScript {
                $_ -ne $ipAddress
            }) -join ','

            Set-Item `
                -Path WSMAN::localhost\Client\TrustedHosts `
                -Value $trustedHosts `
                -Force
            Write-LabMessage -Message $($LocalizedData.RemovingIPAddressFromTrustedHostsMessage `
                -f $VM.Name,$ipAddress)
        }
    } # try
} # Disconnect-LabVM
#EndRegion './Public/Disconnect-LabVm.ps1' 74
#Region './Public/Get-Lab.ps1' 0
function Get-Lab
{
    [CmdLetBinding()]
    [OutputType([XML])]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ConfigPath,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $labPath,

        [Parameter(
            Position = 3)]
        [Switch]
        $SkipXMLValidation
    )

    <#
        If a relative path to the config has been specified
        then convert it to absolute path
    #>

    if (-not [System.IO.Path]::IsPathRooted($ConfigPath))
    {
        $ConfigPath = Join-Path `
            -Path (Get-Location).Path `
            -ChildPath $ConfigPath
    } # if

    if (-not (Test-Path -Path $ConfigPath))
    {
        $exceptionParameters = @{
            errorId       = 'ConfigurationFileNotFoundError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.ConfigurationFileNotFoundError `
                    -f $ConfigPath)
        }
        New-LabException @exceptionParameters
    } # if

    $content = Get-Content -Path $ConfigPath -Raw

    if (-not $content)
    {
        $exceptionParameters = @{
            errorId       = 'ConfigurationFileEmptyError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.ConfigurationFileEmptyError `
                    -f $ConfigPath)
        }
        New-LabException @exceptionParameters
    } # if

    if (-not $SkipXMLValidation)
    {
        # Validate the XML
        Assert-LabValidConfigurationXMLSchema `
            -ConfigPath $ConfigPath `
            -ErrorAction Stop
    }

    # The XML passes the Schema check so load it.
    $lab = New-Object -TypeName System.Xml.XmlDocument
    $lab.PreserveWhitespace = $true
    $lab.LoadXML($content)

    # Check the Required Windows Build
    $requiredWindowsBuild = $lab.labbuilderconfig.settings.requiredwindowsbuild

    if ($requiredWindowsBuild -and `
        ($script:currentBuild -lt $requiredWindowsBuild))
    {
        $exceptionParameters = @{
            errorId       = 'RequiredBuildNotMetError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.RequiredBuildNotMetError `
                    -f $script:currentBuild, $requiredWindowsBuild)
        }
        New-LabException @exceptionParameters
    } # if

    <#
        Figure out the Config path and load it into the XML object (if we can)
        This path is used to find any additional configuration files that might
        be provided with config
    #>

    [System.String] $ConfigPath = [System.IO.Path]::GetDirectoryName($ConfigPath)
    [System.String] $xmlConfigPath = $lab.labbuilderconfig.settings.configpath

    if ($xmlConfigPath)
    {
        $xmlConfigPath = ConvertTo-LabAbsolutePath -Path $xmlConfigPath -BasePath $labPath
    }
    else
    {
        [System.String] $fullConfigPath = $ConfigPath
    }

    $lab.labbuilderconfig.settings.setattribute('fullconfigpath', $fullConfigPath)

    # if the LabPath was passed as a parameter, set it in the config
    if ($labPath)
    {
        $lab.labbuilderconfig.settings.SetAttribute('labpath', $labPath)
    }
    else
    {
        [System.String] $labPath = $lab.labbuilderconfig.settings.labpath
    }

    # Get the VHDParentPathFull - if it isn't supplied default
    [System.String] $vhdParentPath = $lab.labbuilderconfig.settings.vhdparentpath

    if (-not $vhdParentPath)
    {
        $vhdParentPath = 'Virtual Hard Disk Templates'
    }

    # if the resulting parent path is not rooted make the root the Lab Path
    $vhdParentPath = ConvertTo-LabAbsolutePath -Path $vhdParentPath -BasePath $labPath
    $lab.labbuilderconfig.settings.setattribute('vhdparentpathfull', $vhdParentPath)

    # Get the DSCLibraryPathFull - if it isn't supplied default
    [System.String] $dscLibraryPath = $lab.labbuilderconfig.settings.dsclibrarypath

    if (-not $dscLibraryPath)
    {
        $dscLibraryPath = Get-LabBuilderModulePath | Join-Path -ChildPath 'dsclibrary'
    } # if

    # if the resulting parent path is not rooted make the root the Full config path
    $dscLibraryPath = ConvertTo-LabAbsolutePath -Path $dscLibraryPath -BasePath $labPath
    $lab.labbuilderconfig.settings.setattribute('dsclibrarypathfull', $dscLibraryPath)

    # Get the ResourcePathFull - if it isn't supplied default
    [System.String] $resourcePath = $lab.labbuilderconfig.settings.resourcepath

    if (-not $resourcePath)
    {
        $resourcePath = 'Resource'
    } # if

    # if the resulting Resource path is not rooted make the root the Lab Path
    $resourcePath = ConvertTo-LabAbsolutePath -Path $resourcePath -BasePath $labPath
    $lab.labbuilderconfig.settings.setattribute('resourcepathfull', $resourcePath)

    <#
        Determine the ModulePath where alternate Lab PowerShell Modules can be found.
        If a path is specified but it is relative, make it relative to the lab path.
        Otherwise use it as is.
    #>

    [System.String] $modulePath = $lab.labbuilderconfig.settings.modulepath

    if ($modulePath)
    {
        $modulePath = ConvertTo-LabAbsolutePath -Path $modulePath -BasePath $labPath

        # If the path is not included in the PSModulePath add it
        if (-not $env:PSModulePath.ToLower().Contains($modulePath.ToLower() + ';'))
        {
            $env:PSModulePath = "$modulePath;" + $env:PSModulePath
        } # if
    } # if

    return $lab
} # Get-Lab
#EndRegion './Public/Get-Lab.ps1' 174
#Region './Public/Get-LabResourceIso.ps1' 0
function Get-LabResourceISO
{
    [OutputType([LabResourceISO[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $Name
    )

    <#
        Determine the ISORootPath where the ISO files should be found.
        If no path is specified then look in the resource path.
        If a path is specified but it is relative, make it relative to the resource path.
        Otherwise use it as is.
    #>

    [System.String] $isoRootPath = $Lab.labbuilderconfig.Resources.ISOPath

    if ($isoRootPath)
    {
        $isoRootPath = ConvertTo-LabAbsolutePath -Path $isoRootPath `
            -BasePath $Lab.labbuilderconfig.settings.resourcepathfull
    }
    else
    {
        $isoRootPath = $Lab.labbuilderconfig.settings.resourcepathfull
    } # if

    [LabResourceISO[]] $resourceISOs = @()

    if ($Lab.labbuilderconfig.resources)
    {
        foreach ($iso in $Lab.labbuilderconfig.resources.iso)
        {
            $isoName = $iso.Name

            if ($Name -and ($isoName -notin $Name))
            {
                # A names list was passed but this ISO wasn't included
                continue
            } # if

            if ($isoName -eq 'iso')
            {
                $exceptionParameters = @{
                    errorId       = 'ResourceISONameIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.ResourceISONameIsEmptyError)
                }
                New-LabException @exceptionParameters
            } # if

            $resourceISO = [LabResourceISO]::New($isoName)
            $path = $iso.Path

            if ($path)
            {
                $path = ConvertTo-LabAbsolutePath -Path $path -BasePath $isoRootPath
            }
            else
            {
                # A Path is not provided
                $exceptionParameters = @{
                    errorId       = 'ResourceISOPathIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.ResourceISOPathIsEmptyError `
                            -f $isoName)
                }
                New-LabException @exceptionParameters
            }

            if ($iso.URL)
            {
                $resourceISO.URL = $iso.URL
            } # if

            $resourceISO.Path = $path
            $resourceISOs += @( $resourceISO )
        } # foreach
    } # if

    return $resourceISOs
} # Get-LabResourceISO
#EndRegion './Public/Get-LabResourceIso.ps1' 93
#Region './Public/Get-LabResourceModule.ps1' 0
function Get-LabResourceModule
{
    [OutputType([LabResourceModule[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name
    )

    [LabResourceModule[]] $ResourceModules = @()
    if ($Lab.labbuilderconfig.resources)
    {
        foreach ($Module in $Lab.labbuilderconfig.resources.module)
        {
            $ModuleName = $Module.Name
            if ($Name -and ($ModuleName -notin $Name))
            {
                # A names list was passed but this Module wasn't included
                continue
            } # if

            if ($ModuleName -eq 'module')
            {
                $exceptionParameters = @{
                    errorId       = 'ResourceModuleNameIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.ResourceModuleNameIsEmptyError)
                }
                New-LabException @exceptionParameters
            } # if
            $ResourceModule = [LabResourceModule]::New($ModuleName)
            $ResourceModule.URL = $Module.URL
            $ResourceModule.Folder = $Module.Folder
            $ResourceModule.MinimumVersion = $Module.MinimumVersion
            $ResourceModule.RequiredVersion = $Module.RequiredVersion
            $ResourceModules += @( $ResourceModule )
        } # foreach
    } # if
    return $ResourceModules
} # Get-LabResourceModule
#EndRegion './Public/Get-LabResourceModule.ps1' 50
#Region './Public/Get-LabResourceMsu.ps1' 0
function Get-LabResourceMSU
{
    [OutputType([LabResourceMSU[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name
    )

    [LabResourceMSU[]] $ResourceMSUs = @()
    if ($Lab.labbuilderconfig.resources)
    {
        foreach ($MSU in $Lab.labbuilderconfig.resources.msu)
        {
            $MSUName = $MSU.Name
            if ($Name -and ($MSUName -notin $Name))
            {
                # A names list was passed but this MSU wasn't included
                continue
            } # if

            if ($MSUName -eq 'msu')
            {
                $exceptionParameters = @{
                    errorId       = 'ResourceMSUNameIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.ResourceMSUNameIsEmptyError)
                }
                New-LabException @exceptionParameters
            } # if
            $ResourceMSU = [LabResourceMSU]::New($MSUName, $MSU.URL)
            $Path = $MSU.Path
            if ($Path)
            {
                if (-not [System.IO.Path]::IsPathRooted($Path))
                {
                    $Path = Join-Path `
                        -Path $Lab.labbuilderconfig.settings.resourcepathfull `
                        -ChildPath $Path
                }
            }
            else
            {
                $Path = $Lab.labbuilderconfig.settings.resourcepathfull
            }
            $FileName = Join-Path `
                -Path $Path `
                -ChildPath $MSU.URL.Substring($MSU.URL.LastIndexOf('/') + 1)
            $ResourceMSU.Path = $Path
            $ResourceMSU.Filename = $Filename
            $ResourceMSUs += @( $ResourceMSU )
        } # foreach
    } # if
    return $ResourceMSUs
} # Get-LabResourceMSU
#EndRegion './Public/Get-LabResourceMsu.ps1' 65
#Region './Public/Get-LabSwitch.ps1' 0
function Get-LabSwitch
{
    [OutputType([LabSwitch[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name
    )

    [System.String] $LabId = $Lab.labbuilderconfig.settings.labid
    [LabSwitch[]] $Switches = @()
    $ConfigSwitches = $Lab.labbuilderconfig.Switches.Switch

    foreach ($ConfigSwitch in $ConfigSwitches)
    {
        # It can't be switch because if the name attrib/node is missing the name property on the
        # XML object defaults to the name of the parent. So we can't easily tell if no name was
        # specified or if they actually specified 'switch' as the name.
        $SwitchName = $ConfigSwitch.Name
        if ($Name -and ($SwitchName -notin $Name))
        {
            # A names list was passed but this swtich wasn't included
            continue
        } # if

        if ($SwitchName -eq 'switch')
        {
            $exceptionParameters = @{
                errorId       = 'SwitchNameIsEmptyError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.SwitchNameIsEmptyError)
            }
            New-LabException @exceptionParameters
        }

        # Convert the switch type string to a LabSwitchType
        $SwitchType = [LabSwitchType]::$($ConfigSwitch.Type)

        # If the SwitchType string doesn't match any enum value it will be
        # set to null.
        if (-not $SwitchType)
        {
            $exceptionParameters = @{
                errorId       = 'UnknownSwitchTypeError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.UnknownSwitchTypeError `
                        -f $ConfigSwitch.Type, $SwitchName)
            }
            New-LabException @exceptionParameters
        } # if

        # if a LabId is set for the lab, prepend it to the Switch name as long as it isn't
        # an external switch.
        if ($LabId -and ($SwitchType -ne [LabSwitchType]::External))
        {
            $SwitchName = "$LabId$SwitchName"
        } # if

        # Assemble the list of Mangement OS Adapters if any are specified for this switch
        # Only Intenal and External switches are allowed Management OS adapters.
        if ($ConfigSwitch.Adapters)
        {
            [LabSwitchAdapter[]] $ConfigAdapters = @()
            foreach ($Adapter in $ConfigSwitch.Adapters.Adapter)
            {
                $AdapterName = $Adapter.Name
                # if a LabId is set for the lab, prepend it to the adapter name.
                # But only if it is not an External switch.
                if ($LabId -and ($SwitchType -ne [LabSwitchType]::External))
                {
                    $AdapterName = "$LabId$AdapterName"
                }

                $ConfigAdapter = [LabSwitchAdapter]::New($AdapterName)
                $ConfigAdapter.MACAddress = $Adapter.MacAddress
                $ConfigAdapters += @( $ConfigAdapter )
            } # foreach
            if (($ConfigAdapters.Count -gt 0) `
                    -and ($SwitchType -notin [LabSwitchType]::External, [LabSwitchType]::Internal))
            {
                $exceptionParameters = @{
                    errorId       = 'AdapterSpecifiedError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.AdapterSpecifiedError `
                            -f $SwitchType, $SwitchName)
                }
                New-LabException @exceptionParameters
            } # if
        }
        else
        {
            $ConfigAdapters = $null
        } # if

        # Create the new Switch object
        [LabSwitch] $NewSwitch = [LabSwitch]::New($SwitchName, $SwitchType)
        $NewSwitch.VLAN = $ConfigSwitch.VLan
        $NewSwitch.BindingAdapterName = $ConfigSwitch.BindingAdapterName
        $NewSwitch.BindingAdapterMac = $ConfigSwitch.BindingAdapterMac
        $NewSwitch.BindingAdapterMac = $ConfigSwitch.BindingAdapterMac
        $NewSwitch.NatSubnet = $ConfigSwitch.NatSubnet
        $NewSwitch.NatGatewayAddress = $ConfigSwitch.NatGatewayAddress
        $NewSwitch.Adapters = $ConfigAdapters
        $Switches += @( $NewSwitch )
    } # foreach
    return $Switches
} # Get-LabSwitch
#EndRegion './Public/Get-LabSwitch.ps1' 117
#Region './Public/Get-LabVm.ps1' 0
function Get-LabVM
{
    [OutputType([LabVM[]])]
    [CmdLetBinding()]
    param (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $Name,

        [Parameter(
            Position=3)]
        [LabVMTemplate[]]
        $vmTemplates,

        [Parameter(
            Position=4)]
        [LabSwitch[]]
        $switches
    )

    # If VMTeplates array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplates'))
    {
        [LabVMTemplate[]] $vmTemplates = Get-LabVMTemplate `
            -Lab $Lab
    }

    # If Switches array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('Switches'))
    {
        [LabSwitch[]] $switches = Get-LabSwitch `
            -Lab $Lab
    }

    [LabVM[]] $labVMs = @()
    [System.String] $labPath = $Lab.labbuilderconfig.settings.labpath
    [System.String] $labId = $Lab.labbuilderconfig.settings.labid
    $vms = $Lab.labbuilderconfig.vms.vm

    foreach ($vm in $vms)
    {
        if ($vm.Name -eq 'VM')
        {
            $exceptionParameters = @{
                errorId = 'VMNameError'
                errorCategory = 'InvalidArgument'
                errorMessage = $($LocalizedData.VMNameError)
            }
            New-LabException @exceptionParameters
        } # if

        # Get the Instance Count attribute
        $instanceCount = $vm.InstanceCount

        if (-not $instanceCount)
        {
            $instanceCount = 1
        }

        foreach ($instance in 1..$instanceCount)
        {
            # If InstanceCount is 1 then don't increment the IP or MAC addresses or append count to the name
            if ($instanceCount -eq 1)
            {
                $vmName = $vm.Name
                $computerName = $vm.ComputerName
                $incNetIds = 0
            }
            else
            {
                $vmName = "$($vm.Name)$instance"
                $computerName = "$($vm.ComputerName)$instance"
                # This value is used to increment IP and MAC addresses
                $incNetIds = $instance - 1
            } # if

            if ($Name -and ($vmName -notin $Name))
            {
                # A names list was passed but this VM wasn't included
                continue
            } # if

            # If a LabId is set for the lab, prepend it to the VM name.
            if ($labId)
            {
                $vmName = "$labId$vmName"
            }

            if (-not $vm.Template)
            {
                $exceptionParameters = @{
                    errorId = 'VMTemplateNameEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.VMTemplateNameEmptyError `
                        -f $vmName)
                }
                New-LabException @exceptionParameters
            } # if

            # Find the template that this VM uses and get the VHD Path
            [System.String] $parentVHDPath = ''
            [System.Boolean] $found = $false

            foreach ($vmTemplate in $vmTemplates) {
                if ($vmTemplate.Name -eq $vm.Template) {
                    $parentVHDPath = $vmTemplate.ParentVHD
                    $found = $true
                    Break
                } # if
            } # foreach

            if (-not $found)
            {
                $exceptionParameters = @{
                    errorId = 'VMTemplateNotFoundError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.VMTemplateNotFoundError `
                        -f $vmName,$vm.template)
                }
                New-LabException @exceptionParameters
            } # if

            # Get path to Offline Domain Join file if it exists
            [System.String] $nanoODJPath = $null

            if ($vm.NanoODJPath)
            {
                $nanoODJPath = $vm.NanoODJPath
            } # if

            # Assemble the Network adapters that this VM will use
            [LabVMAdapter[]] $vmAdapters = @()
            [System.Int32] $adapterCount = 0

            foreach ($vmAdapter in $vm.Adapters.Adapter)
            {
                $adapterCount++
                $adapterName = $vmAdapter.Name
                $adapterSwitchName = $vmAdapter.SwitchName

                if ($adapterName -eq 'adapter')
                {
                    $exceptionParameters = @{
                        errorId = 'VMAdapterNameError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMAdapterNameError `
                            -f $vmName)
                    }
                    New-LabException @exceptionParameters
                } # if

                if (-not $adapterSwitchName)
                {
                    $exceptionParameters = @{
                        errorId = 'VMAdapterSwitchNameError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMAdapterSwitchNameError `
                            -f $vmName,$adapterName)
                    }
                    New-LabException @exceptionParameters
                } # if

                <#
                    If a LabId is set for the lab, prepend it to the adapter name
                    name and switch name.
                #>

                if ($labId)
                {
                    $adapterName = "$labId$adapterName"
                    $adapterSwitchName = "$labId$adapterSwitchName"
                } # if

                # Check the switch is in the switch list
                $found = $false

                foreach ($switch in $switches)
                {
                    <#
                        Match the switch name to the Adapter Switch Name or
                        the LabId and Adapter Switch Name
                    #>

                    if ($switch.Name -eq $adapterSwitchName)
                    {
                        # The switch is found in the switch list - record the VLAN (if there is one)
                        $found = $true
                        $switchVLan = $switch.Vlan
                        break
                    } # if
                    elseif ($switch.Name -eq $vmAdapter.SwitchName)
                    {
                        # The switch is found in the switch list - record the VLAN (if there is one)
                        $found = $true
                        $switchVLan = $switch.Vlan

                        if ($switch.Type -eq [LabSwitchType]::External)
                        {
                            $adapterName = $vmAdapter.Name
                            $adapterSwitchName = $vmAdapter.SwitchName
                        } # if

                        break
                    }
                } # foreach

                if (-not $found)
                {
                    $exceptionParameters = @{
                        errorId = 'VMAdapterSwitchNotFoundError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMAdapterSwitchNotFoundError `
                            -f $vmName,$adapterName,$adapterSwitchName)
                    }
                    New-LabException @exceptionParameters
                } # if

                # Figure out the VLan - If defined in the VM use it, otherwise use the one defined in the Switch, otherwise keep blank.
                [System.String] $vLan = $vmAdapter.VLan

                if (-not $vLan)
                {
                    $vLan = $switchVLan
                } # if

                [System.Boolean] $MACAddressSpoofing = ($vmAdapter.macaddressspoofing -eq 'On')

                # Have we got any IPv4 settings?
                Remove-Variable -Name IPv4 -ErrorAction SilentlyContinue

                if ($vmAdapter.IPv4)
                {
                    if ($vmAdapter.IPv4.Address)
                    {
                        $ipv4 = [LabVMAdapterIPv4]::New(`
                            (Get-LabNextIpAddress `
                                -IpAddress $vmAdapter.IPv4.Address`
                                -Step $incNetIds)`
                            ,$vmAdapter.IPv4.SubnetMask)
                    } # if

                    $ipv4.defaultgateway = $vmAdapter.IPv4.DefaultGateway
                    $ipv4.dnsserver = $vmAdapter.IPv4.DNSServer
                } # if

                # Have we got any IPv6 settings?
                Remove-Variable -Name IPv6 -ErrorAction SilentlyContinue

                if ($vmAdapter.IPv6)
                {
                    if ($vmAdapter.IPv6.Address)
                    {
                        $ipv6 = [LabVMAdapterIPv6]::New(`
                            (Get-LabNextIpAddress `
                                -IpAddress $vmAdapter.IPv6.Address`
                                -Step $incNetIds)`
                            ,$vmAdapter.IPv6.SubnetMask)
                    } # if

                    $ipv6.defaultgateway = $vmAdapter.IPv6.DefaultGateway
                    $ipv6.dnsserver = $vmAdapter.IPv6.DNSServer
                } # if

                $newVMAdapter = [LabVMAdapter]::New($adapterName)
                $newVMAdapter.SwitchName = $adapterSwitchName

                if ($vmAdapter.macaddress)
                {
                    $newVMAdapter.MACAddress = Get-NextMacAddress `
                        -MacAddress $vmAdapter.macaddress `
                        -Step $incNetIds
                } # if

                $newVMAdapter.MACAddressSpoofing = $MACAddressSpoofing
                $newVMAdapter.VLan = $vLan
                $newVMAdapter.IPv4 = $ipv4
                $newVMAdapter.IPv6 = $ipv6
                $vmAdapters += @( $newVMAdapter )
            } # foreach

            # Assemble the Data Disks this VM will use
            [LabDataVHD[]] $dataVhds = @()
            [System.Int32] $dataVhdCount = 0

            foreach ($vmDataVhd in $vm.DataVhds.DataVhd)
            {
                $dataVhdCount++

                # Load all the VHD properties and check they are valid
                [System.String] $vhd = $vmDataVhd.Vhd

                if (-not $vmDataVhd.Vhd)
                {
                    $exceptionParameters = @{
                        errorId = 'VMDataDiskVHDEmptyError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDataDiskVHDEmptyError `
                            -f $vmName)
                    }
                    New-LabException @exceptionParameters
                } # if

                <#
                    Adjust the path to be relative to the Virtual Hard Disks folder of the VM
                    if it doesn't contain a root (e.g. c:\)
                #>

                if (-not [System.IO.Path]::IsPathRooted($vhd))
                {
                    $vhd = Join-Path `
                        -Path $labPath `
                        -ChildPath "$($vmName)\Virtual Hard Disks\$vhd"
                } # if

                # Does the VHD already exist?
                $exists = Test-Path `
                    -Path $vhd

                # Create the new Data VHD object
                $newDataVHD = [LabDataVHD]::New($vhd)

                # Get the Parent VHD and check it exists if passed
                if ($vmDataVhd.ParentVHD)
                {
                    $newDataVHD.ParentVhd = $vmDataVhd.ParentVHD
                    <#
                        Adjust the path to be relative to the Virtual Hard Disks folder of the VM
                        if it doesn't contain a root (e.g. c:\)
                    #>

                    if (-not [System.IO.Path]::IsPathRooted($newDataVHD.ParentVhd))
                    {
                        $newDataVHD.ParentVhd = Join-Path `
                            -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                            -ChildPath $newDataVHD.ParentVhd
                    }

                    if (-not (Test-Path -Path $newDataVHD.ParentVhd))
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskParentVHDNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskParentVHDNotFoundError `
                                -f $vmName,$newDataVHD.ParentVhd)
                        }
                        New-LabException @exceptionParameters
                    } # if
                } # if

                # Get the Source VHD and check it exists if passed
                if ($vmDataVhd.SourceVHD)
                {
                    $newDataVHD.SourceVhd = $vmDataVhd.SourceVHD
                    <#
                        Adjust the path to be relative to the Virtual Hard Disks folder of the VM
                        if it doesn't contain a root (e.g. c:\)
                    #>

                    if (-not [System.IO.Path]::IsPathRooted($newDataVHD.SourceVhd))
                    {
                        $newDataVHD.SourceVhd = Join-Path `
                            -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                            -ChildPath $newDataVHD.SourceVhd
                    } # if

                    if (-not (Test-Path -Path $newDataVHD.SourceVhd))
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskSourceVHDNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskSourceVHDNotFoundError `
                                -f $vmName,$newDataVHD.SourceVhd)
                        }
                        New-LabException @exceptionParameters
                    } # if
                } # if

                # Get the disk size if provided
                if ($vmDataVhd.Size)
                {
                    $newDataVHD.Size = (Invoke-Expression -Command $vmDataVhd.Size)
                } # if

                # Get the Shared flag
                $newDataVHD.Shared = ($vmDataVhd.Shared -eq 'Y')

                # Get the Support Persistent Reservations
                $newDataVHD.SupportPR = ($vmDataVhd.SupportPR -eq 'Y')

                # Validate the data disk type specified
                if ($vmDataVhd.Type)
                {
                    switch ($vmDataVhd.Type)
                    {
                        'fixed'
                        {
                            break
                        }

                        'dynamic'
                        {
                            break
                        }

                        'differencing'
                        {
                            if (-not $newDataVHD.ParentVhd)
                            {
                                $exceptionParameters = @{
                                    errorId = 'VMDataDiskParentVHDMissingError'
                                    errorCategory = 'InvalidArgument'
                                    errorMessage = $($LocalizedData.VMDataDiskParentVHDMissingError `
                                        -f $vmName)
                                }
                                New-LabException @exceptionParameters
                            } # if

                            if ($newDataVHD.Shared)
                            {
                                $exceptionParameters = @{
                                    errorId = 'VMDataDiskSharedDifferencingError'
                                    errorCategory = 'InvalidArgument'
                                    errorMessage = $($LocalizedData.VMDataDiskSharedDifferencingError `
                                        -f $vmName,$VHD)
                                }
                                New-LabException @exceptionParameters
                            } # if

                            break
                        }

                        default
                        {
                            $exceptionParameters = @{
                                errorId = 'VMDataDiskUnknownTypeError'
                                errorCategory = 'InvalidArgument'
                                errorMessage = $($LocalizedData.VMDataDiskUnknownTypeError `
                                    -f $vmName,$VHD,$vmDataVhd.Type)
                            }
                            New-LabException @exceptionParameters
                        }
                    } # switch

                    $newDataVHD.VHDType = [LabVHDType]::$($vmDataVhd.Type)
                } # if

                # Get Partition Style for the new disk.
                if ($vmDataVhd.PartitionStyle)
                {
                    $PartitionStyle = [LabPartitionStyle]::$($vmDataVhd.PartitionStyle)

                    if (-not $PartitionStyle)
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskPartitionStyleError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskPartitionStyleError `
                                -f $vmName,$VHD,$vmDataVhd.PartitionStyle)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    $newDataVHD.PartitionStyle = $PartitionStyle
                } # if

                # Get file system for the new disk.
                if ($vmDataVhd.FileSystem)
                {
                    $FileSystem = [LabFileSystem]::$($vmDataVhd.FileSystem)

                    if (-not $FileSystem)
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskFileSystemError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskFileSystemError `
                                -f $vmName,$VHD,$vmDataVhd.FileSystem)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    $newDataVHD.FileSystem = $FileSystem
                } # if

                # Has a file system label been provided?
                if ($vmDataVhd.FileSystemLabel)
                {
                    $newDataVHD.FileSystemLabel = $vmDataVhd.FileSystemLabel
                } # if

                <#
                    If the Partition Style, File System or File System Label has been
                    provided then ensure Partition Style and File System are set.
                #>

                if ($newDataVHD.PartitionStyle `
                    -or $newDataVHD.FileSystem `
                    -or $newDataVHD.FileSystemLabel)
                {
                    if (-not $newDataVHD.PartitionStyle)
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskPartitionStyleMissingError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskPartitionStyleMissingError `
                                -f $vmName,$VHD)
                        }
                        New-LabException @exceptionParameters
                    } # if

                    if (-not $newDataVHD.FileSystem)
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskFileSystemMissingError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskFileSystemMissingError `
                                -f $vmName,$VHD)
                        }
                        New-LabException @exceptionParameters
                    } # if
                } # if

                # Get the Folder to copy and check it exists if passed
                if ($vmDataVhd.CopyFolders)
                {
                    foreach ($CopyFolder in ($vmDataVhd.CopyFolders -Split ','))
                    {
                        <#
                            Adjust the path to be relative to the configuration folder
                            if it doesn't contain a root (e.g. c:\)
                        #>

                        if (-not [System.IO.Path]::IsPathRooted($CopyFolder))
                        {
                            $CopyFolder = Join-Path `
                                -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                                -ChildPath $CopyFolder
                        } # if

                        if (-not (Test-Path -Path $CopyFolder -Type Container))
                        {
                            $exceptionParameters = @{
                                errorId = 'VMDataDiskCopyFolderMissingError'
                                errorCategory = 'InvalidArgument'
                                errorMessage = $($LocalizedData.VMDataDiskCopyFolderMissingError `
                                    -f $vmName,$VHD,$CopyFolder)
                                }
                            New-LabException @exceptionParameters
                        }
                    } # foreach

                    $newDataVHD.CopyFolders = $vmDataVhd.CopyFolders
                } # if

                # Should the Source VHD be moved rather than copied
                if ($vmDataVhd.MoveSourceVHD)
                {
                    $newDataVHD.MoveSourceVHD = ($vmDataVhd.MoveSourceVHD -eq 'Y')
                    if (-not $newDataVHD.SourceVHD)
                    {
                        $exceptionParameters = @{
                            errorId = 'VMDataDiskSourceVHDIfMoveError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskSourceVHDIfMoveError `
                                -f $vmName,$VHD)
                        }
                        New-LabException @exceptionParameters
                    } # if
                } # if

                # if the data disk file doesn't exist then some basic parameters MUST be provided
                if (-not $exists `
                    -and ( ( ( -not $newDataVHD.VhdType ) -or ( $newDataVHD.Size -eq 0) ) `
                    -and -not $newDataVHD.SourceVhd ) )
                {
                    $exceptionParameters = @{
                        errorId = 'VMDataDiskCantBeCreatedError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDataDiskCantBeCreatedError `
                            -f $vmName,$VHD)
                    }
                    New-LabException @exceptionParameters
                } # if

                $dataVHDs += @( $newDataVHD )
            } # foreach

            # Assemble the DVD Drives this VM will use
            [LabDVDDrive[]] $dvdDrives = @()
            [System.Int32] $dvdDriveCount = 0

            foreach ($vmDVDDrive in $vm.DVDDrives.DVDDrive)
            {
                $dvdDriveCount++

                # Create the new DVD Drive object
                $newDVDDrive = [LabDVDDRive]::New()

                # Load all the DVD Drive properties and check they are valid
                if ($vmDVDDrive.ISO)
                {
                    <#
                        Look the ISO up in the ISO Resources
                        Pull the list of Resource ISOs available if not already pulled from Lab.
                    #>

                    if (-not $resourceISOs)
                    {
                        $resourceISOs = Get-LabResourceISO `
                            -Lab $Lab
                    } # if

                    # Lookup the Resource ISO record
                    $resourceISO = $resourceISOs | Where-Object -Property Name -eq $vmDVDDrive.ISO

                    if (-not $resourceISO)
                    {
                        # The ISO Resource was not found
                        $exceptionParameters = @{
                            errorId = 'VMDVDDriveISOResourceNotFOundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDVDDriveISOResourceNotFOundError `
                                -f $vmName,$vmDVDDrive.ISO)
                        }
                        New-LabException @exceptionParameters
                    } # if

                    # The ISO resource was found so populate the ISO details
                    $newDVDDrive.ISO = $vmDVDDrive.ISO
                    $newDVDDrive.Path = $resourceISO.Path
                } # if

                $dvdDrives += @( $newDVDDrive )
            } # foreach

            # Does the VM have an Unattend file specified?
            $unattendFile = ''

            if ($vm.UnattendFile)
            {
                if ([System.IO.Path]::IsPathRooted($vm.UnattendFile))
                {
                    $unattendFile = $vm.UnattendFile
                }
                else
                {
                    $unattendFile = Join-Path `
                        -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                        -ChildPath $vm.UnattendFile
                } # if

                if (-not (Test-Path $unattendFile))
                {
                    $exceptionParameters = @{
                        errorId = 'UnattendFileMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.UnattendFileMissingError `
                            -f $vmName,$unattendFile)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if

            # Does the VM specify a Setup Complete Script?
            $setupComplete = ''

            if ($vm.SetupComplete)
            {
                if ([System.IO.Path]::IsPathRooted($vm.SetupComplete))
                {
                    $setupComplete = $vm.SetupComplete
                }
                else
                {
                    $setupComplete = Join-Path `
                        -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                        -ChildPath $vm.SetupComplete
                } # if

                if ([System.IO.Path]::GetExtension($setupComplete).ToLower() -notin '.ps1','.cmd' )
                {
                    $exceptionParameters = @{
                        errorId = 'SetupCompleteFileBadTypeError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.SetupCompleteFileBadTypeError `
                            -f $vmName,$setupComplete)
                    }
                    New-LabException @exceptionParameters
                } # if

                if (-not (Test-Path $setupComplete))
                {
                    $exceptionParameters = @{
                        errorId = 'SetupCompleteFileMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.SetupCompleteFileMissingError `
                            -f $vmName,$setupComplete)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if

            # Create the Lab DSC object
            $labDSC = [LabDSC]::New($vm.DSC.ConfigName)

            # Load the DSC Config File setting and check it
            $labDSC.ConfigFile = ''

            if ($vm.DSC.ConfigFile)
            {
                if (-not [System.IO.Path]::IsPathRooted($vm.DSC.ConfigFile))
                {
                    $labDSC.ConfigFile = Join-Path `
                        -Path $Lab.labbuilderconfig.settings.dsclibrarypathfull `
                        -ChildPath $vm.DSC.ConfigFile
                }
                else
                {
                    $labDSC.ConfigFile = $vm.DSC.ConfigFile
                } # if

                if ([System.IO.Path]::GetExtension($labDSC.ConfigFile).ToLower() -ne '.ps1' )
                {
                    $exceptionParameters = @{
                        errorId = 'DSCConfigFileBadTypeError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.DSCConfigFileBadTypeError `
                            -f $vmName,$labDSC.ConfigFile)
                    }
                    New-LabException @exceptionParameters
                } # if

                if (-not (Test-Path $labDSC.ConfigFile))
                {
                    $exceptionParameters = @{
                        errorId = 'DSCConfigFileMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.DSCConfigFileMissingError `
                            -f $vmName,$labDSC.ConfigFile)
                    }
                    New-LabException @exceptionParameters
                } # if

                if (-not $vm.DSC.ConfigName)
                {
                    $exceptionParameters = @{
                        errorId = 'DSCConfigNameIsEmptyError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.DSCConfigNameIsEmptyError `
                            -f $vmName)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if

            # Load the DSC Parameters
            $labDSC.Parameters = ''

            if ($vm.DSC.Parameters)
            {
                <#
                    Correct any LFs into CRLFs to ensure the new line format is the same when
                    pulled from the XML.
                #>

                $labDSC.Parameters = ($vm.DSC.Parameters -replace "`r`n","`n") -replace "`n","`r`n"
            } # if

            # Load the DSC Parameters
            $labDSC.Logging = ($vm.DSC.Logging -eq 'Y')

            # Get the Memory Startup Bytes (from the template or VM)
            [System.Int64] $memoryStartupBytes = 1GB

            if ($vm.memorystartupbytes)
            {
                $memoryStartupBytes = (Invoke-Expression -Command $vm.memorystartupbytes)
            }
            elseif ($vmTemplate.memorystartupbytes)
            {
                $memoryStartupBytes = $vmTemplate.memorystartupbytes
            } # if

            # Get the Dynamic Memory Enabled flag
            $dynamicMemoryEnabled = $true

            if ($vm.DynamicMemoryEnabled)
            {
                $dynamicMemoryEnabled = ($vm.DynamicMemoryEnabled -eq 'Y')
            }
            elseif ($vmTemplate.DynamicMemoryEnabled)
            {
                $dynamicMemoryEnabled = $vmTemplate.DynamicMemoryEnabled
            } # if

            # Get the Number of vCPUs (from the template or VM)
            [System.Int32] $processorCount = 1

            if ($vm.processorcount)
            {
                $processorCount = (Invoke-Expression $vm.processorcount)
            }
            elseif ($vmTemplate.processorcount)
            {
                $processorCount = $vmTemplate.processorcount
            } # if

            # Get the Expose Virtualization Extensions flag
            if ($vm.ExposeVirtualizationExtensions)
            {
                $exposeVirtualizationExtensions = ($vm.ExposeVirtualizationExtensions -eq 'Y')
            }
            elseif ($vmTemplate.ExposeVirtualizationExtensions)
            {
                $exposeVirtualizationExtensions = $vmTemplate.ExposeVirtualizationExtensions
            } # if

            <#
                If VM requires ExposeVirtualizationExtensions but
                it is not supported on Host then throw an exception.
            #>

            if ($exposeVirtualizationExtensions -and ($script:currentBuild -lt 10565))
            {
                $exceptionParameters = @{
                    errorId = 'VMVirtualizationExtError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.VMVirtualizationExtError `
                        -f $vmName)
                }
                New-LabException @exceptionParameters
            } # if

            $useDifferencingDisk = $true

            if ($vm.UseDifferencingDisk -eq 'N')
            {
                $useDifferencingDisk = $false
            } # if

            # Get the Integration Services flags
            if ($null -ne $vm.IntegrationServices)
            {
                $integrationServices = $vm.IntegrationServices
            }
            elseif ($null -ne $vmTemplate.IntegrationServices)
            {
                $integrationServices = $vmTemplate.IntegrationServices
            } # if

            # Get the Administrator password (from the template or VM)
            $administratorPassword = ''

            if ($vm.administratorpassword)
            {
                $administratorPassword = $vm.administratorpassword
            }
            elseif ($vmTemplate.administratorpassword)
            {
                $administratorPassword = $vmTemplate.administratorpassword
            } # if

            # Get the Product Key (from the template or VM)
            $productKey = ''

            if ($vm.productkey)
            {
                $productKey = $vm.productkey
            }
            elseif ($vmTemplate.productkey)
            {
                $productKey = $vmTemplate.productkey
            } # if

            # Get the Timezone (from the template or VM)
            $timezone = 'Pacific Standard Time'

            if ($vm.timezone)
            {
                $timezone = $vm.timezone
            }
            elseif ($vmTemplate.timezone)
            {
                $timezone = $vmTemplate.timezone
            } # if

            # Get the OS Type
            $osType = [LabOStype]::Server

            if ($vm.OSType)
            {
                $osType = $vm.OSType
            }
            elseif ($vmTemplate.OSType)
            {
                $osType = $vmTemplate.OSType
            } # if

            # Get the Bootorder
            [Byte] $bootorder = [Byte]::MaxValue

            if ($vm.bootorder)
            {
                $bootorder = $vm.bootorder
            } # if

            # Get the Packages
            [System.String] $packages = $null

            if ($vm.packages)
            {
                $packages = $vm.packages
            }
            elseif ($vmTemplate.packages)
            {
                $packages = $vmTemplate.packages
            } # if

            # Get the Version (from the template or VM)
            $version = '8.0'

            if ($vm.version)
            {
                $version = $vm.version
            }
            elseif ($vmTemplate.version)
            {
                $version = $vmTemplate.version
            } # if

            # Get the Generation (from the template or VM)
            $generation = '2'

            if ($vm.generation)
            {
                $generation = $vm.generation
            }
            elseif ($vmTemplate.generation)
            {
                $generation = $vmTemplate.generation
            } # if

            # Get the Certificate Source
            $certificateSource = [LabCertificateSource]::Guest

            if ($osType -eq [LabOSType]::Nano)
            {
                # Nano Server can't generate certificates so must always be set to Host
                $certificateSource = [LabCertificateSource]::Host
            }
            elseif ($vm.CertificateSource)
            {
                $certificateSource = $vm.CertificateSource
            } # if


            $labVM = [LabVM]::New($vmName,$computerName)
            $labVM.Template = $vm.Template
            $labVM.ParentVHD = $parentVHDPath
            $labVM.UseDifferencingDisk = $useDifferencingDisk
            $labVM.MemoryStartupBytes = $memoryStartupBytes
            $labVM.DynamicMemoryEnabled = $dynamicMemoryEnabled
            $labVM.ProcessorCount = $processorCount
            $labVM.ExposeVirtualizationExtensions = $exposeVirtualizationExtensions
            $labVM.IntegrationServices = $integrationServices
            $labVM.AdministratorPassword = $administratorPassword
            $labVM.ProductKey = $productKey
            $labVM.TimeZone =$timezone
            $labVM.UnattendFile = $unattendFile
            $labVM.SetupComplete = $setupComplete
            $labVM.OSType = $osType
            $labVM.CertificateSource = $certificateSource
            $labVM.Bootorder = $bootorder
            $labVM.Packages = $packages
            $labVM.Version = $version
            $labVM.Generation = $generation
            $labVM.Adapters = $vmAdapters
            $labVM.DataVHDs = $dataVHDs
            $labVM.DVDDrives = $dvdDrives
            $labVM.DSC = $labDSC
            $labVM.NanoODJPath = $nanoODJPath
            $labVM.VMRootPath = Join-Path `
                -Path $labPath `
                -ChildPath $vmName
            $labVM.LabBuilderFilesPath = Join-Path `
                -Path $labPath `
                -ChildPath "$vmName\LabBuilder Files"
            $labVMs += @( $labVM )
        } # foreach
    } # foreach

    return $labVMs
} # Get-LabVM
#EndRegion './Public/Get-LabVm.ps1' 990
#Region './Public/Get-LabVMTemplate.ps1' 0
function Get-LabVMTemplate
{
    [OutputType([LabVMTemplate[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabVMTemplateVHD[]] $VMTemplateVHDs
    )

    # if VMTeplateVHDs array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplateVHDs'))
    {
        [LabVMTemplateVHD[]] $VMTemplateVHDs = Get-LabVMTemplateVHD `
            -Lab $Lab
    } # if

    [LabVMTemplate[]] $VMTemplates = @()
    [System.String] $VHDParentPath = $Lab.labbuilderconfig.settings.vhdparentpathfull

    # Get a list of all templates in the Hyper-V system matching the phrase found in the fromvm
    # config setting
    [System.String] $FromVM = $Lab.labbuilderconfig.templates.fromvm
    if ($FromVM)
    {
        $Templates = @(Get-VM -Name $FromVM)
        foreach ($Template in $Templates)
        {
            if ($Name -and ($Template.Name -notin $Name))
            {
                # A names list was passed but this VM Template wasn't included
                continue
            } # if

            [System.String] $VHDFilepath = (Get-VMHardDiskDrive -VMName $Template.Name).Path
            [System.String] $VHDFilename = [System.IO.Path]::GetFileName($VHDFilepath)
            [LabVMTemplate] $VMTemplate = [LabVMTemplate]::New($Template.Name)
            $VMTemplate.Vhd = $VHDFilename
            $VMTemplate.SourceVhd = $VHDFilepath
            $VMTemplate.ParentVhd = (Join-Path -Path $VHDParentPath -ChildPath $VHDFilename)
            $VMTemplates += @( $VMTemplate )
        } # foreach
    } # if

    # Read the list of templates from the configuration file
    $Templates = $Lab.labbuilderconfig.templates.template
    foreach ($Template in $Templates)
    {
        # It can't be template because if the name attrib/node is missing the name property on
        # the XML object defaults to the name of the parent. So we can't easily tell if no name
        # was specified or if they actually specified 'template' as the name.
        $TemplateName = $Template.Name
        if ($Name -and ($TemplateName -notin $Name))
        {
            # A names list was passed but this VM Template wasn't included
            continue
        } # if

        if ($TemplateName -eq 'template')
        {
            $exceptionParameters = @{
                errorId       = 'EmptyTemplateNameError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.EmptyTemplateNameError)
            }
            New-LabException @exceptionParameters
        } # if

        # Does the template already exist in the list?
        [System.Boolean] $Found = $false
        foreach ($VMTemplate in $VMTemplates)
        {
            if ($VMTemplate.Name -eq $TemplateName)
            {
                # The template already exists - so don't add it again
                $Found = $true
                Break
            } # if
        } # foreach
        if (-not $Found)
        {
            # The template wasn't found in the list of templates so add it
            $VMTemplate = [LabVMTemplate]::New($TemplateName)
            # Add the new Template to the Templates Array
            $VMTemplates += @( $VMTemplate )
        } # if

        # Determine the Source VHD, Template VHD and VHD
        [System.String] $SourceVHD = $Template.SourceVHD
        [System.String] $TemplateVHD = $Template.TemplateVHD

        # Throw an error if both a TemplateVHD and SourceVHD are provided
        if ($TemplateVHD -and $SourceVHD)
        {
            $exceptionParameters = @{
                errorId       = 'TemplateSourceVHDAndTemplateVHDConflictError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.TemplateSourceVHDAndTemplateVHDConflictError `
                        -f $TemplateName)
            }
            New-LabException @exceptionParameters
        } # if

        if ($TemplateVHD)
        {
            # A TemplateVHD was provided so look it up.
            $VMTemplateVHD = `
                $VMTemplateVHDs | Where-Object -Property Name -EQ $TemplateVHD
            if ($VMTemplateVHD)
            {
                # The TemplateVHD was found
                $VMTemplate.Sourcevhd = $VMTemplateVHD.VHDPath

                # if a VHD filename wasn't specified in the TemplateVHD
                # Just use the leaf of the SourceVHD
                if ($VMTemplateVHD.VHD)
                {
                    $VMTemplate.Vhd = $VMTemplateVHD.VHD
                }
                else
                {
                    $VMTemplate.Vhd = Split-Path `
                        -Path $VMTemplate.sourcevhd `
                        -Leaf
                } # if
            }
            else
            {
                # The TemplateVHD could not be found in the list
                $exceptionParameters = @{
                    errorId       = 'TemplateTemplateVHDNotFoundError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.TemplateTemplateVHDNotFoundError `
                            -f $TemplateName, $TemplateVHD)
                }
                New-LabException @exceptionParameters
            } # if
        }
        elseif ($SourceVHD)
        {
            # A Source VHD was provided so use that.
            # if this is a relative path, add it to the config path
            if ([System.IO.Path]::IsPathRooted($SourceVHD))
            {
                $VMTemplate.SourceVhd = $SourceVHD
            }
            else
            {
                $VMTemplate.SourceVhd = Join-Path `
                    -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                    -ChildPath $SourceVHD
            }

            # A Source VHD file was specified - does it exist?
            if (-not (Test-Path -Path $VMTemplate.sourcevhd))
            {
                $exceptionParameters = @{
                    errorId       = 'TemplateSourceVHDNotFoundError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.TemplateSourceVHDNotFoundError `
                            -f $TemplateName, $VMTemplate.sourcevhd)
                }
                New-LabException @exceptionParameters
            } # if

            # if a VHD filename wasn't specified in the Template
            # Just use the leaf of the SourceVHD
            if ($Template.VHD)
            {
                $VMTemplate.vhd = $Template.VHD
            }
            else
            {
                $VMTemplate.vhd = Split-Path `
                    -Path $VMTemplate.sourcevhd `
                    -Leaf
            } # if
        }
        elseif ($VMTemplate.SourceVHD)
        {
            # A SourceVHD is already set
            # Usually because it was pulled From a Hyper-V VM template.
        }
        else
        {
            # Neither a SourceVHD or TemplateVHD was provided
            # So throw an exception
            $exceptionParameters = @{
                errorId       = 'TemplateSourceVHDandTemplateVHDMissingError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.TemplateSourceVHDandTemplateVHDMissingError `
                        -f $TemplateName)
            }
            New-LabException @exceptionParameters
        } # if

        # Ensure the ParentVHD is up-to-date
        $VMTemplate.parentvhd = Join-Path `
            -Path $VHDParentPath `
            -ChildPath ([System.IO.Path]::GetFileName($VMTemplate.vhd))

        # Write any template specific default VM attributes
        [System.Int64] $MemoryStartupBytes = 1GB
        if ($Template.MemoryStartupBytes)
        {
            $MemoryStartupBytes = (Invoke-Expression $Template.MemoryStartupBytes)
        } # if
        if ($MemoryStartupBytes -gt 0)
        {
            $VMTemplate.memorystartupbytes = $MemoryStartupBytes
        } # if
        if ($Template.DynamicMemoryEnabled)
        {
            $VMTemplate.DynamicMemoryEnabled = ($Template.DynamicMemoryEnabled -eq 'Y')
        }
        elseif (-not $VMTemplate.DynamicMemoryEnabled)
        {
            $VMTemplate.DynamicMemoryEnabled = $true
        } # if
        if ($Template.version)
        {
            $VMTemplate.version = $Template.version
        }
        elseif (-not $Template.version)
        {
            $VMTemplate.version = "8.0"
        } # if
        if ($Template.generation)
        {
            $VMTemplate.generation = $Template.generation
        }
        elseif (-not $Template.generation)
        {
            $VMTemplate.generation = 2
        } # if

        if ($Template.ProcessorCount)
        {
            $VMTemplate.ProcessorCount = $Template.ProcessorCount
        } # if
        if ($Template.ExposeVirtualizationExtensions)
        {
            $VMTemplate.ExposeVirtualizationExtensions = ($Template.ExposeVirtualizationExtensions -eq 'Y')
        } # if
        if ($Template.AdministratorPassword)
        {
            $VMTemplate.AdministratorPassword = $Template.AdministratorPassword
        } # if
        if ($Template.ProductKey)
        {
            $VMTemplate.ProductKey = $Template.ProductKey
        } # if
        if ($Template.TimeZone)
        {
            $VMTemplate.TimeZone = $Template.TimeZone
        } # if

        if ($Template.OSType)
        {
            $VMTemplate.OSType = [LabOSType]::$($Template.OSType)
        }
        elseif (-not $VMTemplate.OSType)
        {
            $VMTemplate.OSType = [LabOStype]::Server
        } # if
        if ($Template.IntegrationServices)
        {
            $VMTemplate.IntegrationServices = $Template.IntegrationServices
        }
        else
        {
            $VMTemplate.IntegrationServices = $null
        } # if
        if ($Template.Packages)
        {
            $VMTemplate.Packages = $Template.Packages
        }
        else
        {
            $VMTemplate.Packages = $null
        } # if
    } # foreach
    Return $VMTemplates
} # Get-LabVMTemplate
#EndRegion './Public/Get-LabVMTemplate.ps1' 297
#Region './Public/Get-LabVmTemplateVhd.ps1' 0
function Get-LabVMTemplateVHD
{
    [OutputType([LabVMTemplateVHD[]])]
    [CmdLetBinding()]
    param
    (
        [Parameter (
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name
    )

    # return null if the TemplateVHDs node does not exist
    if (-not $Lab.labbuilderconfig.TemplateVHDs)
    {
        return
    }

    # Determine the ISORootPath where the ISO files should be found
    # if no path is specified then look in the same path as the config
    # if a path is specified but it is relative, make it relative to the
    # config path. Otherwise use it as is.
    [System.String] $ISORootPath = $Lab.labbuilderconfig.TemplateVHDs.ISOPath
    if (-not $ISORootPath)
    {
        $ISORootPath = $Lab.labbuilderconfig.settings.fullconfigpath
    }
    else
    {
        if (-not [System.IO.Path]::IsPathRooted($ISORootPath))
        {
            $ISORootPath = Join-Path `
                -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                -ChildPath $ISORootPath
        } # if
    } # if
    if (-not (Test-Path -Path $ISORootPath -Type Container))
    {
        $exceptionParameters = @{
            errorId       = 'VMTemplateVHDISORootPathNotFoundError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.VMTemplateVHDISORootPathNotFoundError `
                    -f $ISORootPath)
        }
        New-LabException @exceptionParameters
    } # if

    # Determine the VHDRootPath where the VHD files should be put
    # if no path is specified then look in the same path as the config
    # if a path is specified but it is relative, make it relative to the
    # config path. Otherwise use it as is.
    [System.String] $VHDRootPath = $Lab.labbuilderconfig.TemplateVHDs.VHDPath
    if (-not $VHDRootPath)
    {
        $VHDRootPath = $Lab.labbuilderconfig.settings.fullconfigpath
    }
    else
    {
        if (-not [System.IO.Path]::IsPathRooted($VHDRootPath))
        {
            $VHDRootPath = Join-Path `
                -Path $Lab.labbuilderconfig.settings.fullconfigpath `
                -ChildPath $VHDRootPath
        } # if
    } # if
    if (-not (Test-Path -Path $VHDRootPath -Type Container))
    {
        $exceptionParameters = @{
            errorId       = 'VMTemplateVHDRootPathNotFoundError'
            errorCategory = 'InvalidArgument'
            errorMessage  = $($LocalizedData.VMTemplateVHDRootPathNotFoundError `
                    -f $VHDRootPath)
        }
        New-LabException @exceptionParameters
    } # if

    $TemplatePrefix = $Lab.labbuilderconfig.templatevhds.prefix

    # Read the list of templateVHD from the configuration file
    $TemplateVHDs = $Lab.labbuilderconfig.templatevhds.templatevhd
    [LabVMTemplateVHD[]] $VMTemplateVHDs = @()
    foreach ($TemplateVHD in $TemplateVHDs)
    {
        # It can't be template because if the name attrib/node is missing the name property on
        # the XML object defaults to the name of the parent. So we can't easily tell if no name
        # was specified or if they actually specified 'templatevhd' as the name.
        $TemplateVHDName = $TemplateVHD.Name
        if ($Name -and ($TemplateVHDName -notin $Name))
        {
            # A names list was passed but this VM Template VHD wasn't included
            continue
        } # if

        if (($TemplateVHDName -eq 'TemplateVHD') `
                -or ([System.String]::IsNullOrWhiteSpace($TemplateVHDName)))
        {
            $exceptionParameters = @{
                errorId       = 'EmptyVMTemplateVHDNameError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.EmptyVMTemplateVHDNameError)
            }
            New-LabException @exceptionParameters
        } # if

        # Get the ISO Path
        [System.String] $ISOPath = $TemplateVHD.ISO
        if (-not $ISOPath)
        {
            $exceptionParameters = @{
                errorId       = 'EmptyVMTemplateVHDISOPathError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.EmptyVMTemplateVHDISOPathError `
                        -f $TemplateVHD.Name)
            }
            New-LabException @exceptionParameters
        } # if

        # Adjust the ISO Path if required
        if (-not [System.IO.Path]::IsPathRooted($ISOPath))
        {
            $ISOPath = Join-Path `
                -Path $ISORootPath `
                -ChildPath $ISOPath
        } # if

        # Does the ISO Exist?
        if (-not (Test-Path -Path $ISOPath))
        {
            $URL = $TemplateVHD.URL
            if ($URL)
            {
                Write-LabMessage `
                    -Type Alert `
                    -Message $($LocalizedData.ISONotFoundDownloadURLMessage `
                        -f $TemplateVHD.Name, $ISOPath, $URL)
            } # if
            $exceptionParameters = @{
                errorId       = 'VMTemplateVHDISOPathNotFoundError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.VMTemplateVHDISOPathNotFoundError `
                        -f $TemplateVHD.Name, $ISOPath)
            }
            New-LabException @exceptionParameters
        } # if

        # Get the VHD Path
        [System.String] $VHDPath = $TemplateVHD.VHD
        if (-not $VHDPath)
        {
            $exceptionParameters = @{
                errorId       = 'EmptyVMTemplateVHDPathError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.EmptyVMTemplateVHDPathError `
                        -f $TemplateVHD.Name)
            }
            New-LabException @exceptionParameters
        } # if

        # Adjust the VHD Path if required
        if (-not [System.IO.Path]::IsPathRooted($VHDPath))
        {
            $VHDPath = Join-Path `
                -Path $VHDRootPath `
                -ChildPath $VHDPath
        } # if

        # Add the template prefix to the VHD name.
        if (-not ([System.String]::IsNullOrWhitespace($TemplatePrefix)))
        {
            $VHDPath = Join-Path `
                -Path (Split-Path -Path $VHDPath)`
                -ChildPath ("$TemplatePrefix$(Split-Path -Path $VHDPath -Leaf)")
        } # if

        # Get the Template OS Type
        $OSType = [LabOStype]::Server
        if ($TemplateVHD.OSType)
        {
            $OSType = [LabOStype]::$($TemplateVHD.OSType)
        } # if
        if (-not $OSType)
        {
            $exceptionParameters = @{
                errorId       = 'InvalidVMTemplateVHDOSTypeError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.InvalidVMTemplateVHDOSTypeError `
                        -f $TemplateVHD.Name, $TemplateVHD.OSType)
            }
            New-LabException @exceptionParameters
        } # if

        # Get the Template Wim Image to use
        $Edition = $null
        if ($TemplateVHD.Edition)
        {
            $Edition = $TemplateVHD.Edition
        } # if

        # Get the Template VHD Format
        $VHDFormat = [LabVHDFormat]::VHDx
        if ($TemplateVHD.VHDFormat)
        {
            $VHDFormat = [LabVHDFormat]::$($TemplateVHD.VHDFormat)
        } # if
        if (-not $VHDFormat)
        {
            $exceptionParameters = @{
                errorId       = 'InvalidVMTemplateVHDVHDFormatError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.InvalidVMTemplateVHDVHDFormatError `
                        -f $TemplateVHD.Name, $TemplateVHD.VHDFormat)
            }
            New-LabException @exceptionParameters
        }

        # Get the Template VHD Type
        $VHDType = [LabVHDType]::Dynamic
        if ($TemplateVHD.VHDType)
        {
            $VHDType = [LabVHDType]::$($TemplateVHD.VHDType)
        } # if
        if (-not $VHDType)
        {
            $exceptionParameters = @{
                errorId       = 'InvalidVMTemplateVHDVHDTypeError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.InvalidVMTemplateVHDVHDTypeError `
                        -f $TemplateVHD.Name, $TemplateVHD.VHDType)
            }
            New-LabException @exceptionParameters
        } # if

        # Get the disk size if provided
        [System.Int64] $VHDSize = 25GB
        if ($TemplateVHD.VHDSize)
        {
            $VHDSize = (Invoke-Expression $TemplateVHD.VHDSize)
        } # if

        # Get the Template VM Generation
        [System.Int32] $Generation = 2
        if ($TemplateVHD.Generation)
        {
            $Generation = $TemplateVHD.Generation
        } # if
        if ($Generation -notin @(1, 2) )
        {
            $exceptionParameters = @{
                errorId       = 'InvalidVMTemplateVHDGenerationError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.InvalidVMTemplateVHDGenerationError `
                        -f $TemplateVHD.Name, $Generation)
            }
            New-LabException @exceptionParameters
        }

        # Get the Template Packages
        if ($TemplateVHD.packages)
        {
            $Packages = $TemplateVHD.Packages
        } # if

        # Get the Template Features
        if ($TemplateVHD.features)
        {
            $Features = $TemplateVHD.Features
        } # if

        # Add template VHD to the list
        $NewVMTemplateVHD = [LabVMTemplateVHD]::New($TemplateVHDName)
        $NewVMTemplateVHD.ISOPath = $ISOPath
        $NewVMTemplateVHD.VHDPath = $VHDPath
        $NewVMTemplateVHD.OSType = $OSType
        $NewVMTemplateVHD.Edition = $Edition
        $NewVMTemplateVHD.Generation = $Generation
        $NewVMTemplateVHD.VHDFormat = $VHDFormat
        $NewVMTemplateVHD.VHDType = $VHDType
        $NewVMTemplateVHD.VHDSize = $VHDSize
        $NewVMTemplateVHD.Packages = $Packages
        $NewVMTemplateVHD.Features = $Features
        $VMTemplateVHDs += @( $NewVMTemplateVHD )
    } # foreach
    Return $VMTemplateVHDs
} # Get-LabVMTemplateVHD
#EndRegion './Public/Get-LabVmTemplateVhd.ps1' 291
#Region './Public/Initialize-LabResourceIso.ps1' 0
function Initialize-LabResourceISO
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabResourceISO[]] $ResourceISOs
    )

    # if resource ISOs was not passed, pull it.
    if (-not $PSBoundParameters.ContainsKey('resourceisos'))
    {
        $ResourceMSUs = Get-LabResourceISO `
            @PSBoundParameters
    } # if

    if ($ResourceISOs)
    {
        foreach ($ResourceISO in $ResourceISOs)
        {
            if (-not (Test-Path -Path $ResourceISO.Path))
            {
                # The Resource ISO does not exist
                if (-not ($ResourceISO.URL))
                {
                    $exceptionParameters = @{
                        errorId       = 'ResourceISOFileNotFoundAndNoURLError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.ResourceISOFileNotFoundAndNoURLError `
                                -f $ISOName, $Path)
                    }
                    New-LabException @exceptionParameters
                } # if

                $URLLeaf = [System.IO.Path]::GetFileName($ResourceISO.URL)
                $URLExtension = [System.IO.Path]::GetExtension($URLLeaf)
                if ($URLExtension -in @('.zip', '.iso'))
                {
                    Write-LabMessage -Message $($LocalizedData.DownloadingResourceISOMessage `
                            -f $ResourceISO.Name, $ResourceISO.URL)

                    Invoke-LabDownloadAndUnzipFile `
                        -URL $ResourceISO.URL `
                        -DestinationPath (Split-Path -Path $ResourceISO.Path)
                }
                elseif ([System.String]::IsNullOrEmpty($URLExtension))
                {
                    Write-LabMessage `
                        -Type Alert `
                        -Message $($LocalizedData.ISONotFoundDownloadURLMessage `
                            -f $ResourceISO.Name, $ResourceISO.Path, $ResourceISO.URL)
                } # if
                if (-not (Test-Path -Path $ResourceISO.Path))
                {
                    $exceptionParameters = @{
                        errorId       = 'ResourceISOFileNotDownloadedError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.ResourceISOFileNotDownloadedError `
                                -f $ResourceISO.Name, $ResourceISO.Path, $ResourceISO.URL)
                    }
                    New-LabException @exceptionParameters
                } # if
            } # if
        } # foreach
    } # if
} # Initialize-LabResourceISO
#EndRegion './Public/Initialize-LabResourceIso.ps1' 79
#Region './Public/Initialize-LabResourceModule.ps1' 0
function Initialize-LabResourceModule
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabResourceModule[]] $ResourceModules
    )

    # if resource modules was not passed, pull it.
    if (-not $PSBoundParameters.ContainsKey('resourcemodules'))
    {
        $ResourceModules = Get-LabResourceModule `
            @PSBoundParameters
    }

    if ($ResourceModules)
    {
        foreach ($Module in $ResourceModules)
        {
            $Splat = [PSObject] @{ Name = $Module.Name }
            if ($Module.URL)
            {
                $Splat += [PSObject] @{ URL = $Module.URL }
            }
            if ($Module.Folder)
            {
                $Splat += [PSObject] @{ Folder = $Module.Folder }
            }
            if ($Module.RequiredVersion)
            {
                $Splat += [PSObject] @{ RequiredVersion = $Module.RequiredVersion }
            }
            if ($Module.MiniumVersion)
            {
                $Splat += [PSObject] @{ MiniumVersion = $Module.MiniumVersion }
            }

            Write-LabMessage -Message $($LocalizedData.DownloadingResourceModuleMessage `
                    -f $Name, $URL)

            Invoke-LabDownloadResourceModule @Splat
        } # foreach
    } # if
} # Initialize-LabResourceModule
#EndRegion './Public/Initialize-LabResourceModule.ps1' 58
#Region './Public/Initialize-LabResourceMsu.ps1' 0
function Initialize-LabResourceMSU
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabResourceMSU[]] $ResourceMSUs
    )

    # if resource MSUs was not passed, pull it.
    if (-not $PSBoundParameters.ContainsKey('resourcemsus'))
    {
        $ResourceMSUs = Get-LabResourceMSU `
            @PSBoundParameters
    }

    if ($ResourceMSUs)
    {
        foreach ($MSU in $ResourceMSUs)
        {
            if (-not (Test-Path -Path $MSU.Filename))
            {
                Write-LabMessage -Message $($LocalizedData.DownloadingResourceMSUMessage `
                        -f $MSU.Name, $MSU.URL)

                Invoke-LabDownloadAndUnzipFile `
                    -URL $MSU.URL `
                    -DestinationPath (Split-Path -Path $MSU.Filename)
            } # if
        } # foreach
    } # if
} # Initialize-LabResourceMSU
#EndRegion './Public/Initialize-LabResourceMsu.ps1' 45
#Region './Public/Initialize-LabSwitch.ps1' 0
function Initialize-LabSwitch
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabSwitch[]] $Switches
    )

    # if switches was not passed, pull it.
    if (-not $PSBoundParameters.ContainsKey('switches'))
    {
        [LabSwitch[]] $Switches = Get-LabSwitch `
            @PSBoundParameters
    }

    # Create Hyper-V Switches
    foreach ($VMSwitch in $Switches)
    {
        if ($Name -and ($VMSwitch.name -notin $Name))
        {
            # A names list was passed but this swtich wasn't included
            continue
        } # if

        if ((Get-VMSwitch | Where-Object -Property Name -eq $($VMSwitch.Name)).Count -eq 0)
        {
            [System.String] $SwitchName = $VMSwitch.Name
            if (-not $SwitchName)
            {
                $exceptionParameters = @{
                    errorId       = 'SwitchNameIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.SwitchNameIsEmptyError)
                }
                New-LabException @exceptionParameters
            }
            [LabSwitchType] $SwitchType = $VMSwitch.Type
            Write-LabMessage -Message $($LocalizedData.CreatingVirtualSwitchMessage `
                    -f $SwitchType, $SwitchName)
            Switch ($SwitchType)
            {
                'External'
                {
                    # Determine which Physical Adapter to bind this switch to
                    if ($VMSwitch.BindingAdapterMac)
                    {
                        $BindingAdapter = Get-NetAdapter | Where-Object {
                            ($_.MacAddress -replace '-', '') -eq $VMSwitch.BindingAdapterMac
                        }
                        $ErrorDetail = "with a MAC address '$($VMSwitch.BindingAdapterMac)' "
                    }
                    elseif ($VMSwitch.BindingAdapterName)
                    {
                        $BindingAdapter = Get-NetAdapter `
                            -Name $VMSwitch.BindingAdapterName `
                            -ErrorAction SilentlyContinue
                        $ErrorDetail = "with a name '$($VMSwitch.BindingAdapterName)' "
                    }
                    else
                    {
                        $BindingAdapter = Get-NetAdapter | `
                            Where-Object {
                            ($_.Status -eq 'Up') `
                                -and (-not $_.Virtual) `
                        } | Select-Object -First 1
                        $ErrorDetail = ''
                    } # if

                    # Check that a Binding Adapter was found
                    if (-not $BindingAdapter)
                    {
                        $exceptionParameters = @{
                            errorId       = 'BindingAdapterNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.BindingAdapterNotFoundError `
                                    -f $SwitchName, $ErrorDetail)
                        }
                        New-LabException @exceptionParameters
                    } # if

                    # Check this adapter is not already bound to a switch
                    $VMSwitchNames = (Get-VMSwitch | Where-Object {
                            $_.SwitchType -eq 'External'
                        }).Name
                    $MacAddress = @()

                    foreach ($VmSwitchName in $VmSwitchNames)
                    {
                        $MacAddress += (Get-VMNetworkAdapter `
                                -ManagementOS `
                                -SwitchName $VmSwitchName `
                                -Name $VmSwitchName `
                                -ErrorAction SilentlyContinue).MacAddress
                    } # foreach

                    $UsedAdapters = @((Get-NetAdapter | Where-Object {
                                ($_.MacAddress -replace '-', '') -in $MacAddress
                            }).Name)
                    if ($BindingAdapter.Name -in $UsedAdapters)
                    {
                        $exceptionParameters = @{
                            errorId       = 'BindingAdapterUsedError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.BindingAdapterUsedError `
                                    -f $SwitchName, $BindingAdapter.Name)
                        }
                        New-LabException @exceptionParameters
                    } # if

                    # Create the swtich
                    $null = New-VMSwitch `
                        -Name $SwitchName `
                        -NetAdapterName $BindingAdapter.Name
                    break
                } # 'External'

                'Private'
                {
                    $null = New-VMSwitch `
                        -Name $SwitchName `
                        -SwitchType Private
                    Break
                } # 'Private'

                'Internal'
                {
                    $null = New-VMSwitch `
                        -Name $SwitchName `
                        -SwitchType Internal
                    Break
                } # 'Internal'

                'NAT'
                {
                    if ($script:currentBuild -lt 14295)
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSwitchNotSupportedError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSwitchNotSupportedError -f $SwitchName)
                        }
                        New-LabException @exceptionParameters
                    }

                    $NatSubnet = $VMSwitch.NatSubnet
                    # Check Nat Subnet is set
                    if (-not $NatSubnet)
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSubnetEmptyError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSubnetEmptyError `
                                    -f $SwitchName)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    # Ensure Nat Subnet looks valid
                    if ($NatSubnet -notmatch '[0-9]+.[0-9]+.[0-9]+.[0-9]+/[0-9]+')
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSubnetInvalidError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSubnetInvalidError `
                                    -f $SwitchName, $NatSubnet)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    $NatSubnetComponents = ($NatSubnet -split '/')
                    $NatSubnetAddress = $NatSubnetComponents[0]
                    # Validate the Nat Subnet Address
                    if (-not ([System.Net.Ipaddress]::TryParse($NatSubnetAddress, [ref]0)))
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSubnetAddressInvalidError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSubnetAddressInvalidError `
                                    -f $SwitchName, $NatSubnetAddress)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    # Validate the Nat Subnet Prefix Length
                    [System.Int32] $NatSubnetPrefixLength = $NatSubnetComponents[1]
                    if (($NatSubnetPrefixLength -lt 1) -or ($NatSubnetPrefixLength -gt 31))
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSubnetPrefixLengthInvalidError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSubnetPrefixLengthInvalidError `
                                    -f $SwitchName, $NatSubnetPrefixLength)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    $NatGatewayAddress = $VMSwitch.NatGatewayAddress

                    # Create the Internal Switch
                    $null = New-VMSwitch `
                        -Name $SwitchName `
                        -SwitchType Internal `
                        -ErrorAction Stop
                    # Set the IP Address on the default adapter connected to the NAT switch
                    $MacAddress = (Get-VMNetworkAdapter `
                            -ManagementOS `
                            -SwitchName $SwitchName `
                            -Name $SwitchName `
                            -ErrorAction Stop).MacAddress
                    if ([System.String]::IsNullOrEmpty($MacAddress))
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSwitchDefaultAdapterMacEmptyError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSwitchDefaultAdapterMacEmptyError `
                                    -f $SwitchName)
                        }
                        New-LabException @exceptionParameters
                    } # if
                    $Adapter = Get-NetAdapter |
                        Where-Object { ($_.MacAddress -replace '-', '') -eq $MacAddress }
                    if (-not $Adapter)
                    {
                        $exceptionParameters = @{
                            errorId       = 'NatSwitchDefaultAdapterNotFoundError'
                            errorCategory = 'InvalidArgument'
                            errorMessage  = $($LocalizedData.NatSwitchDefaultAdapterNotFoundError `
                                    -f $SwitchName)
                        }
                        New-LabException @exceptionParameters
                    }
                    $null = $Adapter | New-NetIPAddress `
                        -IPAddress $NatGatewayAddress `
                        -PrefixLength $NatSubnetPrefixLength `
                        -ErrorAction Stop
                    # Does the NAT already exist?
                    $NetNat = Get-NetNat `
                        -Name $SwitchName `
                        -ErrorAction SilentlyContinue
                    if ($NetNat)
                    {
                        # If the NAT already exists, remove it so it can be recreated
                        $null = $NetNat | Remove-NetNat -Confirm:$false
                    }
                    # Create the new NAT
                    $null = New-NetNat `
                        -Name $SwitchName `
                        -InternalIPInterfaceAddressPrefix $NatSubnet `
                        -ErrorAction Stop
                    Break
                } # 'NAT'
                Default
                {
                    $exceptionParameters = @{
                        errorId       = 'UnknownSwitchTypeError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.UnknownSwitchTypeError `
                                -f $SwitchType, $SwitchName)
                    }
                    New-LabException @exceptionParameters
                }
            } # switch

            if ($SwitchType -ne 'Private')
            {
                # Configure the VLan on the default Management Adapter
                $setLabSwitchAdapterParameters = @{
                    Name       = $SwitchName
                    SwitchName = $SwitchName
                }

                if ($VMSwitch.VLan)
                {
                    $setLabSwitchAdapterParameters += @{
                        VlanId = $VMSwitch.Vlan
                    }
                } # if

                Set-LabSwitchAdapter @setLabSwitchAdapterParameters

                # Add any management OS adapters to the switch
                if ($VMSwitch.Adapters)
                {
                    foreach ($Adapter in $VMSwitch.Adapters)
                    {
                        $setLabSwitchAdapterParameters = @{
                            Name       = $Adapter.Name
                            SwitchName = $SwitchName
                        }

                        if ($Adapter.MacAddress)
                        {
                            $setLabSwitchAdapterParameters += @{
                                StaticMacAddress = $Adapter.MacAddress
                            }
                        } # if

                        if ($VMSwitch.VLan)
                        {
                            $setLabSwitchAdapterParameters += @{
                                VlanId = $VMSwitch.Vlan
                            }
                        } # if

                        Set-LabSwitchAdapter @setLabSwitchAdapterParameters
                    } # foreach
                } # if
            } # if
        } # if
    } # foreach
} # Initialize-LabSwitch
#EndRegion './Public/Initialize-LabSwitch.ps1' 321
#Region './Public/Initialize-LabVm.ps1' 0
function Initialize-LabVM
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position=3)]
        [LabVM[]] $VMs
    )

    # if VMs array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMs'))
    {
        [LabVM[]] $VMs = Get-LabVM `
            @PSBoundParameters
    } # if

    # if there are not VMs just return
    if (-not $VMs)
    {
        return
    } # if

    $CurrentVMs = Get-VM

    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # Figure out the name of the LabBuilder control switch
    $ManagementSwitchName = Get-LabManagementSwitchName `
        -Lab $Lab
    if ($Lab.labbuilderconfig.switches.ManagementVlan)
    {
        [Int32] $ManagementVlan = $Lab.labbuilderconfig.switches.ManagementVlan
    }
    else
    {
        [Int32] $ManagementVlan = $script:DefaultManagementVLan
    } # if

    foreach ($VM in $VMs)
    {
        if ($Name -and ($VM.Name -notin $Name))
        {
            # A names list was passed but this VM wasn't included
            continue
        } # if

        # Get the root path of the VM
        [System.String] $VMRootPath = $VM.VMRootPath

        # Get the Virtual Machine Path
        [System.String] $VMPath = Join-Path `
            -Path $VMRootPath `
            -ChildPath 'Virtual Machines'

        # Get the Virtual Hard Disk Path
        [System.String] $VHDPath = Join-Path `
            -Path $VMRootPath `
            -ChildPath 'Virtual Hard Disks'

        # Get Path to LabBuilder files
        [System.String] $VMLabBuilderFiles = $VM.LabBuilderFilesPath

        if (($CurrentVMs | Where-Object -Property Name -eq $VM.Name).Count -eq 0)
        {
            Write-LabMessage -Message $($LocalizedData.CreatingVMMessage `
                -f $VM.Name)

            # Make sure the appropriate folders exist
            Initialize-LabVMPath `
                -VMPath $VMRootPath

            # Create the boot disk
            $VMBootDiskPath = "$VHDPath\$($VM.Name) Boot Disk.vhdx"
            if (-not (Test-Path -Path $VMBootDiskPath))
            {
                if ($VM.UseDifferencingDisk)
                {
                    Write-LabMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                        -f $VM.Name,$VMBootDiskPath,'Differencing Boot')

                    $null = New-VHD `
                        -Differencing `
                        -Path $VMBootDiskPath `
                        -ParentPath $VM.ParentVHD
                }
                else
                {
                    Write-LabMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                        -f $VM.Name,$VMBootDiskPath,'Boot')

                    $null = Copy-Item `
                        -Path $VM.ParentVHD `
                        -Destination $VMBootDiskPath
                }

                # Create all the required initialization files for this VM
                New-LabVMInitializationFile `
                    -Lab $Lab `
                    -VM $VM

                # Because this is a new boot disk apply any required initialization
                Initialize-LabBootVHD `
                    -Lab $Lab `
                    -VM $VM `
                    -VMBootDiskPath $VMBootDiskPath
            }
            else
            {
                Write-LabMessage -Message $($LocalizedData.VMDiskAlreadyExistsMessage `
                    -f $VM.Name,$VMBootDiskPath,'Boot')
            } # if

            # Create New VM from settings
            if ($VM.Version -and ($script:currentBuild -ge 14352))
            {
                $null = New-VM `
                    -Name $VM.Name `
                    -MemoryStartupBytes $VM.MemoryStartupBytes `
                    -Generation $VM.Generation `
                    -Path $LabPath `
                    -VHDPath $VMBootDiskPath `
                    -Version $VM.Version
            }

            else
            {
                $null = New-VM `
                    -Name $VM.Name `
                    -MemoryStartupBytes $VM.MemoryStartupBytes `
                    -Generation $VM.Generation `
                    -Path $LabPath `
                    -VHDPath $VMBootDiskPath `


            }

            # Remove the default network adapter created with the VM because we don't need it
            Remove-VMNetworkAdapter `
                -VMName $VM.Name `
                -Name 'Network Adapter'
        }

        # Set the processor count if different to default and if specified in config file
        if ($VM.ProcessorCount)
        {
            if ($VM.ProcessorCount -ne (Get-VM -Name $VM.Name).ProcessorCount)
            {
                Set-VM `
                    -Name $VM.Name `
                    -ProcessorCount $VM.ProcessorCount
            } # if
        } # if

        # Enable/Disable Dynamic Memory
        Write-Verbose -Message "Checking Dynamic Memory: $($VM.DynamicMemoryEnabled) = $((Get-VMMemory -VMName $VM.Name).DynamicMemoryEnabled)" -Verbose
        if ($VM.DynamicMemoryEnabled -ne (Get-VMMemory -VMName $VM.Name).DynamicMemoryEnabled)
        {
            Write-Verbose -Message "Checking Dynamic Memory: $($VM.DynamicMemoryEnabled)" -Verbose
            Set-VMMemory `
                -VMName $VM.Name `
                -DynamicMemoryEnabled:$($VM.DynamicMemoryEnabled)
        } # if

        # Is ExposeVirtualizationExtensions supported?
        if ($script:currentBuild -lt 10565)
        {
            # No, it is not supported - is it required by VM?
            if ($VM.ExposeVirtualizationExtensions)
            {
                # ExposeVirtualizationExtensions is required for this VM
                $exceptionParameters = @{
                    errorId = 'VMVirtualizationExtError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.VMVirtualizationExtError `
                        -f $VM.Name)
                }
                New-LabException @exceptionParameters
            } # if
        }
        else
        {
            # Yes, it is - is the setting different?
            if ($VM.ExposeVirtualizationExtensions `
                -ne (Get-VMProcessor -VMName $VM.Name).ExposeVirtualizationExtensions)
            {
                if ($script:currentBuild -ge 14352 -and ($VM.Version -eq "8.0"))
                {
                    Set-VMSecurity `
                        -VMName $VM.Name `
                        -VirtualizationBasedSecurityOptOut $true
                } # if
                # Try and update it
                Set-VMProcessor `
                    -VMName $VM.Name `
                    -ExposeVirtualizationExtensions:$VM.ExposeVirtualizationExtensions `
                    -ErrorAction Stop
            } # if
        } # if

        # Enable/Disable the Integration Services
        Update-LabVMIntegrationService `
            -VM $VM

        # Update the data disks for the VM
        Update-LabVMDataDisk `
            -Lab $Lab `
            -VM $VM

        # Update the DVD Drives for the VM
        Update-LabVMDvdDrive `
            -Lab $Lab `
            -VM $VM

        # Create/Update the Management Network Adapter
        if ((Get-VMNetworkAdapter -VMName $VM.Name | Where-Object -Property Name -EQ $ManagementSwitchName).Count -eq 0)
        {
            Write-LabMessage -Message $($LocalizedData.AddingVMNetworkAdapterMessage `
                -f $VM.Name,$ManagementSwitchName,'Management')

            Add-VMNetworkAdapter `
                -VMName $VM.Name `
                -SwitchName $ManagementSwitchName `
                -Name $ManagementSwitchName
        }
        $VMNetworkAdapter = Get-VMNetworkAdapter `
            -VMName $VM.Name `
            -Name $ManagementSwitchName
        $null = $VMNetworkAdapter |
            Set-VMNetworkAdapterVlan `
                -Access `
                -VlanId $ManagementVlan

        Write-LabMessage -Message $($LocalizedData.SettingVMNetworkAdapterVlanMessage `
            -f $VM.Name,$ManagementSwitchName,'Management',$ManagementVlan)

        # Create any network adapters
        foreach ($VMAdapter in $VM.Adapters)
        {
            if ((Get-VMNetworkAdapter -VMName $VM.Name | Where-Object -Property Name -EQ $VMAdapter.Name).Count -eq 0)
            {
                Write-LabMessage -Message $($LocalizedData.AddingVMNetworkAdapterMessage `
                    -f $VM.Name,$VMAdapter.SwitchName,$VMAdapter.Name)

                Add-VMNetworkAdapter `
                    -VMName $VM.Name `
                    -SwitchName $VMAdapter.SwitchName `
                    -Name $VMAdapter.Name
            } # if

            $VMNetworkAdapter = Get-VMNetworkAdapter `
                -VMName $VM.Name `
                -Name $VMAdapter.Name
            if ($VMAdapter.VLan)
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapterVlan `
                        -Access `
                        -VlanId $VMAdapter.VLan

                Write-LabMessage -Message $($LocalizedData.SettingVMNetworkAdapterVlanMessage `
                    -f $VM.Name,$VMAdapter.Name,'',$VMAdapter.VLan)
            }
            else
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapterVlan `
                        -Untagged

                Write-LabMessage -Message $($LocalizedData.ClearingVMNetworkAdapterVlanMessage `
                    -f $VM.Name,$VMAdapter.Name,'')
            } # if

            if ([System.String]::IsNullOrWhitespace($VMAdapter.MACAddress))
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapter `
                        -DynamicMacAddress
            }
            else
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapter `
                        -StaticMacAddress $VMAdapter.MACAddress
            } # if

            # Enable Device Naming if supported by VM version and generation
            if (((Get-Command -Name Set-VMNetworkAdapter).Parameters.ContainsKey('DeviceNaming')) -and (($VM.Version -ge "6.2") -and ($VM.Generation -eq 2)))
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapter `
                        -DeviceNaming On
            } # if
            if ($VMAdapter.MACAddressSpoofing -ne $VMNetworkAdapter.MACAddressSpoofing)
            {
                $MACAddressSpoofing = if ($VMAdapter.MACAddressSpoofing) {'On'} else {'Off'}
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapter `
                        -MacAddressSpoofing $MACAddressSpoofing
            } # if
        } # foreach

        Install-LabVM `
            -Lab $Lab `
            -VM $VM
    } # foreach
} # Initialize-LabVM
#EndRegion './Public/Initialize-LabVm.ps1' 319
#Region './Public/Initialize-LabVmTemplate.ps1' 0
function Initialize-LabVMTemplate
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabVMTemplate[]] $VMTemplates,

        [Parameter(
            Position = 4)]
        [LabVMTemplateVHD[]] $VMTemplateVHDs
    )

    # if VMTeplates array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplates'))
    {
        [LabVMTemplate[]] $VMTemplates = Get-LabVMTemplate `
            @PSBoundParameters
    }

    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # Check each Parent VHD exists in the Parent VHDs folder for the
    # Lab. If it isn't, try and copy it from the SourceVHD
    # Location.
    foreach ($VMTemplate in $VMTemplates)
    {
        if ($Name -and ($VMTemplate.Name -notin $Name))
        {
            # A names list was passed but this VM Template wasn't included
            continue
        } # if

        if (-not (Test-Path $VMTemplate.ParentVhd))
        {
            # The Parent VHD isn't in the VHD Parent folder
            # so copy it there, optimize it and mark it read-only.
            if (-not (Test-Path $VMTemplate.SourceVhd))
            {
                # The source VHD could not be found.
                $exceptionParameters = @{
                    errorId       = 'TemplateSourceVHDNotFoundError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.TemplateSourceVHDNotFoundError `
                            -f $VMTemplate.Name, $VMTemplate.sourcevhd)
                }
                New-LabException @exceptionParameters
            }

            Write-LabMessage -Message $($LocalizedData.CopyingTemplateSourceVHDMessage `
                    -f $VMTemplate.SourceVhd, $VMTemplate.ParentVhd)
            Copy-Item `
                -Path $VMTemplate.SourceVhd `
                -Destination $VMTemplate.ParentVhd

            # Add any packages to the template if required
            if (-not [System.String]::IsNullOrWhitespace($VMTemplate.Packages))
            {
                if ($VMTemplate.OSType -ne [LabOStype]::Nano)
                {
                    # Mount the Template Boot VHD so that files can be loaded into it
                    Write-LabMessage -Message $($LocalizedData.MountingTemplateBootDiskMessage `
                            -f $VMTemplate.Name, $VMTemplate.ParentVhd)

                    # Create a mount point for mounting the Boot VHD
                    [System.String] $MountPoint = Join-Path `
                        -Path (Split-Path -Path $VMTemplate.ParentVHD) `
                        -ChildPath 'Mount'

                    if (-not (Test-Path -Path $MountPoint -PathType Container))
                    {
                        $null = New-Item `
                            -Path $MountPoint `
                            -ItemType Directory
                    }

                    # Mount the VHD to the Mount point
                    $null = Mount-WindowsImage `
                        -ImagePath $VMTemplate.parentvhd `
                        -Path $MountPoint `
                        -Index 1

                    # Get the list of Packages to apply
                    $ApplyPackages = @($VMTemplate.Packages -split ',')

                    # Get the list of Lab Resource MSUs
                    $ResourceMSUs = Get-LabResourceMSU `
                        -Lab $Lab

                    foreach ($Package in $ApplyPackages)
                    {
                        # Find the package in the Resources
                        [System.Boolean] $Found = $false
                        foreach ($ResourceMSU in $ResourceMSUs)
                        {
                            if ($ResourceMSU.Name -eq $Package)
                            {
                                # Found the package
                                $Found = $true
                                break
                            } # if
                        } # foreach
                        if (-not $Found)
                        {
                            # Dismount before throwing the error
                            Write-LabMessage -Message $($LocalizedData.DismountingTemplateBootDiskMessage `
                                    -f $VMTemplate.Name, $VMTemplate.parentvhd)
                            $null = Dismount-WindowsImage `
                                -Path $MountPoint `
                                -Save
                            $null = Remove-Item `
                                -Path $MountPoint `
                                -Recurse `
                                -Force

                            $exceptionParameters = @{
                                errorId       = 'PackageNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.PackageNotFoundError `
                                        -f $Package)
                            }
                            New-LabException @exceptionParameters
                        } # if

                        $PackagePath = $ResourceMSU.Filename
                        if (-not (Test-Path -Path $PackagePath))
                        {
                            # Dismount before throwing the error
                            Write-LabMessage -Message $($LocalizedData.DismountingTemplateBootDiskMessage `
                                    -f $VMTemplate.Name, $VMTemplate.ParentVhd)
                            $null = Dismount-WindowsImage `
                                -Path $MountPoint `
                                -Save
                            $null = Remove-Item `
                                -Path $MountPoint `
                                -Recurse `
                                -Force

                            $exceptionParameters = @{
                                errorId       = 'PackageMSUNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.PackageMSUNotFoundError `
                                        -f $Package, $PackagePath)
                            }
                            New-LabException @exceptionParameters
                        } # if

                        # Apply a Pacakge
                        Write-LabMessage -Message $($LocalizedData.ApplyingTemplateBootDiskFileMessage `
                                -f $VMTemplate.Name, $Package, $PackagePath)

                        $null = Add-WindowsPackage `
                            -PackagePath $PackagePath `
                            -Path $MountPoint
                    } # foreach

                    # Dismount the VHD
                    Write-LabMessage -Message $($LocalizedData.DismountingTemplateBootDiskMessage `
                            -f $VMTemplate.Name, $VMTemplate.parentvhd)
                    $null = Dismount-WindowsImage `
                        -Path $MountPoint `
                        -Save
                    $null = Remove-Item `
                        -Path $MountPoint `
                        -Recurse `
                        -Force
                } # if
            } # if

            Write-LabMessage -Message $($LocalizedData.OptimizingParentVHDMessage `
                    -f $VMTemplate.parentvhd)
            Set-ItemProperty `
                -Path $VMTemplate.parentvhd `
                -Name IsReadOnly `
                -Value $false
            Optimize-VHD `
                -Path $VMTemplate.parentvhd `
                -Mode Full
            Write-LabMessage -Message $($LocalizedData.SettingParentVHDReadonlyMessage `
                    -f $VMTemplate.parentvhd)
            Set-ItemProperty `
                -Path $VMTemplate.parentvhd `
                -Name IsReadOnly `
                -Value $true
        }
        Else
        {
            Write-LabMessage -Message $($LocalizedData.SkipParentVHDFileMessage `
                    -f $VMTemplate.Name, $VMTemplate.parentvhd)
        }

        # if this is a Nano Server template, we need to ensure that the
        # NanoServerPackages folder is copied to our Lab folder
        if ($VMTemplate.OSType -eq [LabOStype]::Nano)
        {
            [System.String] $VHDPackagesFolder = Join-Path `
                -Path (Split-Path -Path $VMTemplate.SourceVhd -Parent)`
                -ChildPath 'NanoServerPackages'

            [System.String] $NanoPackagesFolder = Join-Path `
                -Path $LabPath `
                -ChildPath 'NanoServerPackages'

            if (-not (Test-Path -Path $NanoPackagesFolder -Type Container))
            {
                Write-LabMessage -Message $($LocalizedData.CachingNanoServerPackagesMessage `
                        -f $VHDPackagesFolder, $NanoPackagesFolder)
                Copy-Item `
                    -Path $VHDPackagesFolder `
                    -Destination $LabPath `
                    -Recurse `
                    -Force
            }
        }
    }
}
#EndRegion './Public/Initialize-LabVmTemplate.ps1' 229
#Region './Public/Initialize-LabVmTemplateVhd.ps1' 0
function Initialize-LabVMTemplateVHD
{
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabVMTemplateVHD[]] $VMTemplateVHDs
    )

    # if VMTeplateVHDs array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplateVHDs'))
    {
        [LabVMTemplateVHD[]] $VMTemplateVHDs = Get-LabVMTemplateVHD `
            @PSBoundParameters
    } # if

    # if there are no VMTemplateVHDs just return
    if ($null -eq $VMTemplateVHDs)
    {
        return
    } # if

    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # Is an alternate path to DISM specified?
    if ($Lab.labbuilderconfig.settings.DismPath)
    {
        $DismPath = Join-Path `
            -Path $Lab.labbuilderconfig.settings.DismPath `
            -ChildPath 'dism.exe'
        if (-not (Test-Path -Path $DismPath))
        {
            $exceptionParameters = @{
                errorId       = 'FileNotFoundError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.FileNotFoundError `
                        -f 'alternate DISM.EXE', $DismPath)
            }
            New-LabException @exceptionParameters
        }
    }

    foreach ($VMTemplateVHD in $VMTemplateVHDs)
    {
        [System.String] $TemplateVHDName = $VMTemplateVHD.Name
        if ($Name -and ($TemplateVHDName -notin $Name))
        {
            # A names list was passed but this VM Template VHD wasn't included
            continue
        } # if

        [System.String] $VHDPath = $VMTemplateVHD.VHDPath

        if (Test-Path -Path ($VHDPath))
        {
            # The SourceVHD already exists
            Write-LabMessage -Message $($LocalizedData.SkipVMTemplateVHDFileMessage `
                    -f $TemplateVHDName, $VHDPath)

            continue
        } # if

        # Create the VHD
        Write-LabMessage -Message $($LocalizedData.CreatingVMTemplateVHDMessage `
                -f $TemplateVHDName, $VHDPath)

        # Check the ISO exists.
        [System.String] $ISOPath = $VMTemplateVHD.ISOPath
        if (-not (Test-Path -Path $ISOPath))
        {
            $exceptionParameters = @{
                errorId       = 'VMTemplateVHDISOPathNotFoundError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.VMTemplateVHDISOPathNotFoundError `
                        -f $TemplateVHDName, $ISOPath)
            }
            New-LabException @exceptionParameters
        } # if

        # Mount the ISO so we can read the files.
        Write-LabMessage -Message $($LocalizedData.MountingVMTemplateVHDISOMessage `
                -f $TemplateVHDName, $ISOPath)

        $null = Mount-DiskImage `
            -ImagePath $ISOPath `
            -StorageType ISO `
            -Access Readonly

        # Refresh the PS Drive list to make sure the new drive can be detected
        Get-PSDrive `
            -PSProvider FileSystem

        $DiskImage = Get-DiskImage -ImagePath $ISOPath
        $Volume = Get-Volume -DiskImage $DiskImage
        if (-not $Volume)
        {
            $exceptionParameters = @{
                errorId       = 'VolumeNotAvailableAfterMountError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.VolumeNotAvailableAfterMountError `
                        -f $ISOPath)
            }
            New-LabException @exceptionParameters
        }
        [System.String] $DriveLetter = $Volume.DriveLetter
        if (-not $DriveLetter)
        {
            $exceptionParameters = @{
                errorId       = 'DriveLetterNotAssignedError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.DriveLetterNotAssignedError `
                        -f $ISOPath)
            }
            New-LabException @exceptionParameters
        }
        [System.String] $ISODrive = "$([System.String]$DriveLetter):"

        # Determine the path to the WIM
        [System.String] $SourcePath = "$ISODrive\Sources\Install.WIM"
        if ($VMTemplateVHD.OSType -eq [LabOStype]::Nano)
        {
            $SourcePath = "$ISODrive\Nanoserver\NanoServer.WIM"
        } # if

        # This will have to change depending on the version
        # of Convert-WindowsImage being used.
        [System.String] $VHDFormat = $VMTemplateVHD.VHDFormat
        [System.String] $VHDType = $VMTemplateVHD.VHDType
        [System.String] $VHDDiskLayout = 'UEFI'
        if ($VMTemplateVHD.Generation -eq 1)
        {
            $VHDDiskLayout = 'BIOS'
        } # if

        [System.String] $Edition = $VMTemplateVHD.Edition
        # if edition is not set then use Get-WindowsImage to get the name
        # of the first image in the WIM.
        if ([System.String]::IsNullOrWhiteSpace($Edition))
        {
            $Edition = (Get-WindowsImage `
                    -ImagePath $SourcePath `
                    -Index 1).ImageName
        } # if

        $ConvertParams = @{
            sourcepath  = $SourcePath
            vhdpath     = $VHDpath
            vhdformat   = $VHDFormat
            # Convert-WindowsImage doesn't support creating different VHDTypes
            # vhdtype = $VHDType
            edition     = $Edition
            disklayout  = $VHDDiskLayout
            erroraction = 'Stop'
        }

        # Set the size
        if ($null -ne $VMTemplateVHD.VHDSize)
        {
            $ConvertParams += @{
                sizebytes = $VMTemplateVHD.VHDSize
            }
        } # if

        # Are any features specified?
        if (-not [System.String]::IsNullOrWhitespace($VMTemplateVHD.Features))
        {
            $Features = @($VMTemplateVHD.Features -split ',')
            $ConvertParams += @{
                feature = $Features
            }
        } # if

        # Is an alternate path to DISM specified?
        if ($DismPath)
        {
            $ConvertParams += @{
                DismPath = $DismPath
            }
        }

        # Perform Nano Server package prep
        if ($VMTemplateVHD.OSType -eq [LabOStype]::Nano)
        {
            # Make a copy of the all the Nano packages in the VHD root folder
            # So that if any VMs need to add more packages they are accessible
            # once the ISO has been dismounted.
            [System.String] $VHDFolder = Split-Path `
                -Path $VHDPath `
                -Parent

            [System.String] $NanoPackagesFolder = Join-Path `
                -Path $VHDFolder `
                -ChildPath 'NanoServerPackages'

            if (-not (Test-Path -Path $NanoPackagesFolder -Type Container))
            {
                Write-LabMessage -Message $($LocalizedData.CachingNanoServerPackagesMessage `
                        -f "$ISODrive\Nanoserver\Packages", $NanoPackagesFolder)
                Copy-Item `
                    -Path "$ISODrive\Nanoserver\Packages" `
                    -Destination $VHDFolder `
                    -Recurse `
                    -Force
                Rename-Item `
                    -Path "$VHDFolder\Packages" `
                    -NewName 'NanoServerPackages'
            } # if
        } # if

        # Do we need to add any packages?
        if (-not [System.String]::IsNullOrWhitespace($VMTemplateVHD.Packages))
        {
            $Packages = @()

            # Get the list of Lab Resource MSUs
            $ResourceMSUs = Get-LabResourceMSU `
                -Lab $Lab

            try
            {
                foreach ($Package in @($VMTemplateVHD.Packages -split ','))
                {
                    if (([System.IO.Path]::GetExtension($Package) -eq '.cab') `
                            -and ($VMTemplateVHD.OSType -eq [LabOSType]::Nano))
                    {
                        # This is a Nano Server .CAB pacakge
                        # Generate the path to the Nano Package
                        $PackagePath = Join-Path `
                            -Path $NanoPackagesFolder `
                            -ChildPath $Package
                        # Does it exist?
                        if (-not (Test-Path -Path $PackagePath))
                        {
                            $exceptionParameters = @{
                                errorId       = 'NanoPackageNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.NanoPackageNotFoundError `
                                        -f $PackagePath)
                            }
                            New-LabException @exceptionParameters
                        }
                        $Packages += @( $PackagePath )

                        # Generate the path to the Nano Language Package
                        $PackageLangFile = $Package -replace '.cab', "_$($script:NanoPackageCulture).cab"
                        $PackageLangPath = Join-Path `
                            -Path $NanoPackagesFolder `
                            -ChildPath "$($script:NanoPackageCulture)\$PackageLangFile"
                        # Does it exist?
                        if (-not (Test-Path -Path $PackageLangPath))
                        {
                            $exceptionParameters = @{
                                errorId       = 'NanoPackageNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.NanoPackageNotFoundError `
                                        -f $PackageLangPath)
                            }
                            New-LabException @exceptionParameters
                        }
                        $Packages += @( $PackageLangPath )
                    }
                    else
                    {
                        # Tihs is a ResourceMSU type package
                        [System.Boolean] $Found = $false
                        foreach ($ResourceMSU in $ResourceMSUs)
                        {
                            if ($ResourceMSU.Name -eq $Package)
                            {
                                # Found the package
                                $Found = $true
                                break
                            } # if
                        } # foreach
                        if (-not $Found)
                        {
                            $exceptionParameters = @{
                                errorId       = 'PackageNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.PackageNotFoundError `
                                        -f $Package)
                            }
                            New-LabException @exceptionParameters
                        } # if

                        $PackagePath = $ResourceMSU.Filename
                        if (-not (Test-Path -Path $PackagePath))
                        {
                            $exceptionParameters = @{
                                errorId       = 'PackageMSUNotFoundError'
                                errorCategory = 'InvalidArgument'
                                errorMessage  = $($LocalizedData.PackageMSUNotFoundError `
                                        -f $Package, $PackagePath)
                            }
                            New-LabException @exceptionParameters
                        } # if
                        $Packages += @( $PackagePath )
                    }
                } # foreach
                $ConvertParams += @{
                    Package = $Packages
                }
            }
            catch
            {
                # Dismount Disk Image before throwing exception
                $null = Dismount-DiskImage `
                    -ImagePath $ISOPath

                Throw $_
            } # try
        } # if

        Write-LabMessage -Message ($LocalizedData.ConvertingWIMtoVHDMessage `
                -f $SourcePath, $VHDPath, $VHDFormat, $Edition, $VHDPartitionStyle, $VHDType)

        # Work around an issue with Convert-WindowsImage not seeing the drive
        Get-PSDrive `
            -PSProvider FileSystem

        # Dot source the Convert-WindowsImage script
        # Should only be done once
        if (-not (Test-Path -Path Function:Convert-WindowsImage))
        {
            . $script:SupportConvertWindowsImagePath
        } # if

        try
        {
            # Call the Convert-WindowsImage script
            Convert-WindowsImage @ConvertParams
        } # try
        catch
        {
            $exceptionParameters = @{
                errorId       = 'ConvertWindowsImageError'
                errorCategory = 'InvalidArgument'
                errorMessage  = $($LocalizedData.ConvertWindowsImageError `
                        -f $ISOPath, $SourcePath, $Edition, $VHDFormat, $_.Exception.Message)
            }
            New-LabException @exceptionParameters
        } # catch
        finally
        {
            # Dismount the ISO.
            Write-LabMessage -Message $($LocalizedData.DismountingVMTemplateVHDISOMessage `
                    -f $TemplateVHDName, $ISOPath)

            $null = Dismount-DiskImage `
                -ImagePath $ISOPath
        } # finally
    } # endfor
} # Initialize-LabVMTemplateVHD
#EndRegion './Public/Initialize-LabVmTemplateVhd.ps1' 365
#Region './Public/Install-Lab.ps1' 0
function Install-Lab
{
    [CmdLetBinding(DefaultParameterSetName="Lab")]
    param
    (
        [parameter(
            Position=1,
            ParameterSetName="File",
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [parameter(
            Position=2,
            ParameterSetName="File")]
        [ValidateNotNullOrEmpty()]
        [System.String] $LabPath,

        [Parameter(
            Position=3,
            ParameterSetName="Lab",
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=4)]
        [Switch] $CheckEnvironment,

        [Parameter(
            Position=5)]
        [Switch] $Force,

        [Parameter(
            Position=6)]
        [Switch] $OffLine

    ) # Param

    begin
    {
        # Create a splat array containing force if it is set
        $ForceSplat = @{}

        if ($PSBoundParameters.ContainsKey('Force'))
        {
            $ForceSplat = @{ Force = $true }
        } # if

        # Remove some PSBoundParameters so we can Splat
        $null = $PSBoundParameters.Remove('CheckEnvironment')
        $null = $PSBoundParameters.Remove('Force')

        if ($CheckEnvironment)
        {
            # Check Hyper-V
            Install-LabHyperV `
                -ErrorAction Stop
        } # if

        # Ensure WS-Man is enabled
        Enable-LabWSMan `
            @ForceSplat `
            -ErrorAction Stop

        if (!($PSBoundParameters.ContainsKey('OffLine')))
        {
        # Install Package Providers
        Install-LabPackageProvider `
            @ForceSplat `
            -ErrorAction Stop

        # Register Package Sources
        Register-LabPackageSource `
            @ForceSplat `
            -ErrorAction Stop
        }

        $null = $PSBoundParameters.Remove('Offline')

        if ($PSCmdlet.ParameterSetName -eq 'File')
        {
            # Read the configuration
            $Lab = Get-Lab `
                @PSBoundParameters `
                -ErrorAction Stop
        } # if
    } # begin

    process
    {
        # Initialize the core Lab components
        # Check Lab Folder structure
        Write-LabMessage -Message $($LocalizedData.InitializingLabFoldersMesage)

        # Check folders are defined
        [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

        if (-not (Test-Path -Path $LabPath))
        {
            Write-LabMessage -Message $($LocalizedData.CreatingLabFolderMessage `
                -f 'LabPath',$LabPath)

            $null = New-Item `
                -Path $LabPath `
                -Type Directory
        }

        [System.String] $VHDParentPath = $Lab.labbuilderconfig.settings.vhdparentpathfull

        if (-not (Test-Path -Path $VHDParentPath))
        {
            Write-LabMessage -Message $($LocalizedData.CreatingLabFolderMessage `
                -f 'VHDParentPath',$VHDParentPath)

            $null = New-Item `
                -Path $VHDParentPath `
                -Type Directory
        }

        [System.String] $ResourcePath = $Lab.labbuilderconfig.settings.resourcepathfull

        if (-not (Test-Path -Path $ResourcePath))
        {
            Write-LabMessage -Message $($LocalizedData.CreatingLabFolderMessage `
                -f 'ResourcePath',$ResourcePath)

            $null = New-Item `
                -Path $ResourcePath `
                -Type Directory
        }

        # Initialize the Lab Management Switch
        Initialize-LabManagementSwitch `
            -Lab $Lab `
            -ErrorAction Stop

        # Download any Resource Modules required by this Lab
        $ResourceModules = Get-LabResourceModule `
            -Lab $Lab
        Initialize-LabResourceModule `
            -Lab $Lab `
            -ResourceModules $ResourceModules `
            -ErrorAction Stop

        # Download any Resource MSUs required by this Lab
        $ResourceMSUs = Get-LabResourceMSU `
            -Lab $Lab
        Initialize-LabResourceMSU `
            -Lab $Lab `
            -ResourceMSUs $ResourceMSUs `
            -ErrorAction Stop

        # Initialize the Switches
        $Switches = Get-LabSwitch `
            -Lab $Lab
        Initialize-LabSwitch `
            -Lab $Lab `
            -Switches $Switches `
            -ErrorAction Stop

        # Initialize the VM Template VHDs
        $VMTemplateVHDs = Get-LabVMTemplateVHD `
            -Lab $Lab
        Initialize-LabVMTemplateVHD `
            -Lab $Lab `
            -VMTemplateVHDs $VMTemplateVHDs `
            -ErrorAction Stop

        # Initialize the VM Templates
        $VMTemplates = Get-LabVMTemplate `
            -Lab $Lab
        Initialize-LabVMTemplate `
            -Lab $Lab `
            -VMTemplates $VMTemplates `
            -ErrorAction Stop

        # Initialize the VMs
        $VMs = Get-LabVM `
            -Lab $Lab `
            -VMTemplates $VMTemplates `
            -Switches $Switches
        Initialize-LabVM `
            -Lab $Lab `
            -VMs $VMs `
            -ErrorAction Stop

        Write-LabMessage -Message $($LocalizedData.LabInstallCompleteMessage `
            -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath)
    } # process

    end
    {
    } # end
} # Install-Lab
#EndRegion './Public/Install-Lab.ps1' 197
#Region './Public/Install-LabVm.ps1' 0
function Install-LabVM
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [LabVM] $VM
    )

    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # The VM is now ready to be started
    if ((Get-VM -Name $VM.Name).State -eq 'Off')
    {
        Write-LabMessage -Message $($LocalizedData.StartingVMMessage `
            -f $VM.Name)

        Start-VM -VMName $VM.Name
    } # if

    # We only perform this section of VM Initialization (DSC, Cert, etc) with Server OS
    if ($VM.DSC.ConfigFile)
    {
        # Has this VM been initialized before (do we have a cert for it)
        if (-not (Test-Path "$LabPath\$($VM.Name)\LabBuilder Files\$script:DSCEncryptionCert"))
        {
            # No, so check it is initialized and download the cert if required
            if (Wait-LabVMInitializationComplete -VM $VM -ErrorAction Continue)
            {
                Write-LabMessage -Message $($LocalizedData.CertificateDownloadStartedMessage `
                    -f $VM.Name)

                if ($VM.CertificateSource -eq [LabCertificateSource]::Guest)
                {
                    if (Recieve-LabSelfSignedCertificate -Lab $Lab -VM $VM)
                    {
                        Write-LabMessage -Message $($LocalizedData.CertificateDownloadCompleteMessage `
                            -f $VM.Name)
                    }
                    else
                    {
                        $exceptionParameters = @{
                            errorId = 'CertificateDownloadError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.CertificateDownloadError `
                                -f $VM.name)
                        }
                        New-LabException @exceptionParameters
                    } # if
                } # if
            }
            else
            {
                $exceptionParameters = @{
                    errorId = 'InitializationDidNotCompleteError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.InitializationDidNotCompleteError `
                        -f $VM.name)
                }
                New-LabException @exceptionParameters
            } # if
        } # if

        if ($VM.OSType -in ([LabOStype]::Nano))
        {
        # Copy ODJ Files if it Exists
            Copy-LabOdjFile `
                -Lab $Lab `
                -VM $VM
        } # if

        # Create any DSC Files for the VM
        Initialize-LabDSC `
            -Lab $Lab `
            -VM $VM

        # Attempt to start DSC on the VM
        Start-LabDSC `
            -Lab $Lab `
            -VM $VM
    } # if
} # Install-LabVM
#EndRegion './Public/Install-LabVm.ps1' 91
#Region './Public/New-Lab.ps1' 0
function New-Lab
{
    [CmdLetBinding(
        SupportsShouldProcess = $true)]
    [OutputType([XML])]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [Parameter(
            Position=2,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $LabPath,

        [Parameter(
            Position=3,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Name,

        [Parameter(
            Position=4)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Version = '1.0',

        [Parameter(
            Position=5)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Id,

        [Parameter(
            Position=6)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Description,

        [Parameter(
            Position=7)]
        [ValidateNotNullOrEmpty()]
        [System.String] $DomainName,

        [Parameter(
            Position=8)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Email
    ) # Param

    # Determine the full Lab Path
    if (-not [System.IO.Path]::IsPathRooted($LabPath))
    {
        $LabPath = Join-Path `
            -Path Get-Location `
            -ChildPath $LabPath
    } # if

    # Does the Lab Path exist?
    if (Test-Path -Path $LabPath -Type Container)
    {
        # It does - exit if the user declines
        if (-not $PSCmdlet.ShouldProcess( 'LocalHost', `
            ($LocalizedData.ShouldOverwriteLab `
            -f $LabPath )))
        {
            return
        }
    }
    else
    {
        Write-LabMessage -Message $($LocalizedData.CreatingLabFolderMessage `
            -f 'LabPath',$LabPath)

       $null = New-Item `
            -Path $LabPath `
            -Type Directory
    } # if

    # Determine the full Lab configuration Path
    if (-not [System.IO.Path]::IsPathRooted($ConfigPath))
    {
        $ConfigPath = Join-Path `
            -Path $LabPath `
            -ChildPath $ConfigPath
    } # if

    # Does the lab configuration path already exist?
    if (Test-Path -Path $ConfigPath)
    {
        # It does - exit if the user declines
        if (-not $PSCmdlet.ShouldProcess( 'LocalHost', `
            ($LocalizedData.ShouldOverwriteLabConfig `
            -f $ConfigPath )))
        {
            return
        }
    } # if

    # Get the Config Template into a variable
    $Content = Get-Content `
        -Path $script:ConfigurationXMLTemplate

    # The XML passes the Schema check so load it.
    [XML] $Lab = New-Object System.Xml.XmlDocument
    $Lab.PreserveWhitespace = $true
    $Lab.LoadXML($Content)

    # Populate the Lab Entries
    $Lab.labbuilderconfig.name = $Name
    $Lab.labbuilderconfig.version = $Version
    $Lab.labbuilderconfig.settings.labpath = $LabPath
    if ($PSBoundParameters.ContainsKey('Id'))
    {
        $Lab.labbuilderconfig.settings.SetAttribute('Id',$Id)
    } # if
    if ($PSBoundParameters.ContainsKey('Description'))
    {
        $Lab.labbuilderconfig.description = $Description
    } # if
    if ($PSBoundParameters.ContainsKey('DomainName'))
    {
        $Lab.labbuilderconfig.settings.SetAttribute('DomainName',$DomainName)
    } # if
    if ($PSBoundParameters.ContainsKey('Email'))
    {
        $Lab.labbuilderconfig.settings.SetAttribute('Email',$Email)
    } # if

    # Save Configiration XML
    $Lab.Save($ConfigPath)

    # Create ISOFiles folder
    $null = New-Item `
        -Path (Join-Path -Path $LabPath -ChildPath 'ISOFiles')`
        -Type Directory `
        -ErrorAction SilentlyContinue

    # Create VDFFiles folder
    $null = New-Item `
        -Path (Join-Path -Path $LabPath -ChildPath 'VHDFiles')`
        -Type Directory `
        -ErrorAction SilentlyContinue

    # Copy the DSCLibrary
    $null = Copy-Item `
        -Path $script:DSCLibraryPath `
        -Destination $LabPath `
        -Recurse `
        -Force `
        -ErrorAction SilentlyContinue

    Return (Get-Lab `
        -ConfigPath $ConfigPath `
        -LabPath $LabPath)
} # New-Lab
#EndRegion './Public/New-Lab.ps1' 158
#Region './Public/Remove-LabSwitch.ps1' 0
function Remove-LabSwitch
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        $Name,

        [Parameter(
            Position = 3)]
        [LabSwitch[]]
        $Switches,

        [Parameter(
            Position = 4)]
        [Switch]
        $RemoveExternal
    )

    $PSBoundParameters.Remove('RemoveExternal')

    # if switches were not passed so pull them
    if (-not $PSBoundParameters.ContainsKey('switches'))
    {
        [LabSwitch[]] $Switches = Get-LabSwitch `
            @PSBoundParameters
    }

    # Delete Hyper-V Switches
    foreach ($VMSwitch in $Switches)
    {
        if ($Name -and ($VMSwitch.name -notin $Name))
        {
            # A names list was passed but this swtich wasn't included
            continue
        } # if

        $existingVMSwitch = Get-VMSwitch | Where-Object -Property Name -eq $VMSwitch.Name

        if ($existingVMSwitch.Count -ne 0)
        {
            $SwitchName = $VMSwitch.Name

            if (-not $SwitchName)
            {
                $exceptionParameters = @{
                    errorId       = 'SwitchNameIsEmptyError'
                    errorCategory = 'InvalidArgument'
                    errorMessage  = $($LocalizedData.SwitchNameIsEmptyError)
                }
                New-LabException @exceptionParameters
            }

            [LabSwitchType] $SwitchType = $VMSwitch.Type

            Write-LabMessage -Message $($LocalizedData.DeleteingVirtualSwitchMessage `
                    -f $SwitchType, $SwitchName)

            Switch ($SwitchType)
            {
                'External'
                {
                    if ($VMSwitch.Adapters)
                    {
                        $VMSwitch.Adapters.foreach( {
                                $null = Remove-VMNetworkAdapter `
                                    -SwitchName $SwitchName `
                                    -Name $_.Name `
                                    -ManagementOS `
                                    -ErrorAction SilentlyContinue
                            } )
                    } # if

                    if ($RemoveExternal)
                    {
                        Remove-VMSwitch `
                            -Name $SwitchName
                    }
                    break
                } # 'External'

                'Private'
                {
                    Remove-VMSwitch `
                        -Name $SwitchName
                    break
                } # 'Private'

                'Internal'
                {
                    if ($VMSwitch.Adapters)
                    {
                        $VMSwitch.Adapters.foreach( {
                                $null = Remove-VMNetworkAdapter `
                                    -SwitchName $SwitchName `
                                    -Name $_.Name `
                                    -ManagementOS `
                                    -ErrorAction SilentlyContinue
                                } )
                    } # if

                    Remove-VMSwitch `
                        -Name $SwitchName
                    break
                } # 'Internal'

                'NAT'
                {
                    Remove-NetNat `
                        -Name $SwitchName
                    Remove-VMSwitch `
                        -Name $SwitchName
                    break
                } # 'Internal'

                Default
                {
                    $exceptionParameters = @{
                        errorId       = 'UnknownSwitchTypeError'
                        errorCategory = 'InvalidArgument'
                        errorMessage  = $($LocalizedData.UnknownSwitchTypeError `
                                -f $SwitchType, $SwitchName)
                    }
                    New-LabException @exceptionParameters
                }
            } # Switch
        } # if
    } # foreach
}
#EndRegion './Public/Remove-LabSwitch.ps1' 139
#Region './Public/Remove-LabVm.ps1' 0
function Remove-LabVM
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position=3)]
        [LabVM[]] $VMs,

        [Parameter(
            Position=4)]
        [Switch] $RemoveVMFolder
    )

    # if VMs array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMs'))
    {
        $null = $PSBoundParameters.Remove('RemoveVMFolder')
        [LabVM[]] $VMs = Get-LabVM `
            @PSBoundParameters
    } # if

    $CurrentVMs = Get-VM

    # Get the LabPath
    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    foreach ($VM in $VMs)
    {
        if ($Name -and ($VM.Name -notin $Name))
        {
            # A names list was passed but this VM wasn't included
            continue
        } # if

        if (($CurrentVMs | Where-Object -Property Name -eq $VM.Name).Count -ne 0)
        {
            # if the VM is running we need to shut it down.
            if ((Get-VM -Name $VM.Name).State -eq 'Running')
            {
                Write-LabMessage -Message $($LocalizedData.StoppingVMMessage `
                    -f $VM.Name)

                Stop-VM `
                    -Name $VM.Name
                # Wait for it to completely shut down and report that it is off.
                Wait-LabVMOff `
                    -VM $VM
            }

            Write-LabMessage -Message $($LocalizedData.RemovingVMMessage `
                -f $VM.Name)

            # Now delete the actual VM
            Get-VM `
                -Name $VM.Name | Remove-VM -Force -Confirm:$false

            Write-LabMessage -Message $($LocalizedData.RemovedVMMessage `
                -f $VM.Name)
        }
        else
        {
            Write-LabMessage -Message $($LocalizedData.VMNotFoundMessage `
                -f $VM.Name)
        }
    }
    # Should we remove the VM Folder?
    if ($RemoveVMFolder)
    {
        if (Test-Path -Path $VM.VMRootPath)
        {
            Write-LabMessage -Message $($LocalizedData.DeletingVMFolderMessage `
                -f $VM.Name)

            Remove-Item `
                -Path $VM.VMRootPath `
                -Recurse `
                -Force
        }
    }
} # Remove-LabVM
#EndRegion './Public/Remove-LabVm.ps1' 93
#Region './Public/Remove-LabVMTemplate.ps1' 0
function Remove-LabVMTemplate
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabVMTemplate[]] $VMTemplates
    )

    # if VMTeplates array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplates'))
    {
        $VMTemplates = Get-LabVMTemplate `
            @PSBoundParameters
    } # if
    foreach ($VMTemplate in $VMTemplates)
    {
        if ($Name -and ($VMTemplate.Name -notin $Name))
        {
            # A names list was passed but this VM Template wasn't included
            continue
        } # if

        if (Test-Path $VMTemplate.ParentVhd)
        {
            Set-ItemProperty `
                -Path $VMTemplate.parentvhd `
                -Name IsReadOnly `
                -Value $false
            Write-LabMessage -Message $($LocalizedData.DeletingParentVHDMessage `
                    -f $VMTemplate.ParentVhd)
            Remove-Item `
                -Path $VMTemplate.ParentVhd `
                -Confirm:$false `
                -Force
        } # if
    } # foreach
}
#EndRegion './Public/Remove-LabVMTemplate.ps1' 51
#Region './Public/Remove-LabVmTemplateVhd.ps1' 0
function Remove-LabVMTemplateVHD
{
    param
    (
        [Parameter(
            Position = 1,
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Name,

        [Parameter(
            Position = 3)]
        [LabVMTemplateVHD[]] $VMTemplateVHDs
    )

    # if VMTeplateVHDs array not passed, pull it from config.
    if (-not $PSBoundParameters.ContainsKey('VMTemplateVHDs'))
    {
        [LabVMTemplateVHD[]] $VMTemplateVHDs = Get-LabVMTemplateVHD `
            @PSBoundParameters
    } # if

    # if there are no VMTemplateVHDs just return
    if ($null -eq $VMTemplateVHDs)
    {
        return
    } # if

    [System.String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    foreach ($VMTemplateVHD in $VMTemplateVHDs)
    {
        [System.String] $TemplateVHDName = $VMTemplateVHD.Name
        if ($Name -and ($TemplateVHDName -notin $Name))
        {
            # A names list was passed but this VM Template VHD wasn't included
            continue
        } # if

        [System.String] $VHDPath = $VMTemplateVHD.VHDPath

        if (Test-Path -Path ($VHDPath))
        {
            Remove-Item `
                -Path $VHDPath `
                -Force
            Write-LabMessage -Message $($LocalizedData.DeletingVMTemplateVHDFileMessage `
                    -f $TemplateVHDName, $VHDPath)
        } # if
    } # endfor
} # Remove-LabVMTemplateVHD
#EndRegion './Public/Remove-LabVmTemplateVhd.ps1' 57
#Region './Public/Start-Lab.ps1' 0
function Start-Lab
{
    [CmdLetBinding(DefaultParameterSetName="Lab")]
    param
    (
        [parameter(
            Position=1,
            ParameterSetName="File",
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [parameter(
            Position=2,
            ParameterSetName="File")]
        [ValidateNotNullOrEmpty()]
        [System.String] $labPath,

        [Parameter(
            Position=3,
            ParameterSetName="Lab",
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        $lab,

        [Parameter(
            Position=4)]
        [System.Int32] $StartupTimeout = $script:StartupTimeout
    ) # Param

    begin
    {
        # Remove some PSBoundParameters so we can Splat
        $null = $PSBoundParameters.Remove('StartupTimeout')

        if ($PSCmdlet.ParameterSetName -eq 'File')
        {
            # Read the configuration
            $lab = Get-Lab @PSBoundParameters
        } # if
    } # begin

    process
    {
        # Get the VMs
        $vms = Get-LabVM `
            -Lab $lab

        # Get the bootorders by lowest first and ignoring 0 and call
        $bootOrders = @( ($vms |
            Where-Object -FilterScript { ($_.Bootorder -gt 0) } ).Bootorder )
        $bootPhases = @( ($bootOrders | Sort-Object -Unique) )

        # Step through each of these "Bootphases" waiting for them to complete
        foreach ($bootPhase in $bootPhases)
        {
            # Process this "Bootphase"
            Write-LabMessage -Message $($LocalizedData.StartingBootPhaseVMsMessage `
                -f $bootPhase)

            # Get all VMs in this "Bootphase"
            $bootVMs = @( $vms | Where-Object -FilterScript { ($_.BootOrder -eq $bootPhase) } )

            $startPhase = Get-Date
            $phaseComplete = $false
            $phaseAllBooted = $true
            $vmCount = $bootVMs.Count
            $vmNumber = 0

            <#
                Loop through all the VMs in this "Bootphase" repeatedly
                until timeout occurs or PhaseComplete is marked as complete
            #>

            while (-not $phaseComplete `
                -and ((Get-Date) -lt $startPhase.AddSeconds($StartupTimeout)))
            {
                # Get the VM to boot/check
                $vm = $bootVMs[$vmNumber]
                $vmName = $vm.Name

                # Get the actual Hyper-V VM object
                $vmObject = Get-VM `
                    -Name $vmName `
                    -ErrorAction SilentlyContinue

                if ($vmObject)
                {
                    # Start the VM if it is off
                    if ($vmObject.State -eq 'Off')
                    {
                        Write-LabMessage -Message $($LocalizedData.StartingVMMessage -f $vmName)
                        Start-VM -VM $vmObject
                    } # if

                    <#
                        Use the allocation of a Management IP Address as an indicator
                        the machine has booted
                    #>

                    $managementIP = Get-LabVMManagementIPAddress `
                        -Lab $lab `
                        -VM $vm `
                        -ErrorAction SilentlyContinue

                    if (-not ($managementIP))
                    {
                        # It has not booted
                        $phaseAllBooted = $false
                    } # if
                }
                else
                {
                    # if the VM does not exist then throw a non-terminating exception
                    $exceptionParameters = @{
                        errorId = 'VMDoesNotExistError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDoesNotExistError `
                            -f $vmName)

                    }
                    New-LabException @exceptionParameters
                } # if

                $vmNumber++

                if ($vmNumber -eq $vmCount)
                {
                    <#
                        We have stepped through all VMs in this Phase so check
                        if all have booted, otherwise reset the loop.
                    #>

                    if ($phaseAllBooted)
                    {
                        <#
                            If we have gone through all VMs in this "Bootphase"
                            and they're all marked as booted then we can mark
                            this phase as complete and allow moving on to the next one
                        #>

                        Write-LabMessage -Message $($LocalizedData.AllBootPhaseVMsStartedMessage -f $bootPhase)
                        $phaseComplete = $true
                    }
                    else
                    {
                        $phaseAllBooted = $true
                    } # if

                    # Reset the VM Loop
                    $vmNumber = 0
                } # if
            } # while

            # Did we timeout?
            if (-not ($phaseComplete))
            {
                # Yes, throw an exception
                $exceptionParameters = @{
                    errorId = 'BootPhaseVMsTimeoutError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.BootPhaseStartVMsTimeoutError `
                        -f $bootPhase)

                }
                New-LabException @exceptionParameters
            } # if
        } # foreach

        Write-LabMessage -Message $($LocalizedData.LabStartCompleteMessage `
            -f $lab.labbuilderconfig.name,$lab.labbuilderconfig.settings.fullconfigpath)
    } # process

    end
    {
    } # end
} # Start-Lab
#EndRegion './Public/Start-Lab.ps1' 175
#Region './Public/Stop-Lab.ps1' 0
function Stop-Lab
{
    [CmdLetBinding(DefaultParameterSetName="Lab")]
    param
    (
        [parameter(
            Position=1,
            ParameterSetName="File",
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [parameter(
            Position=2,
            ParameterSetName="File")]
        [ValidateNotNullOrEmpty()]
        [System.String] $LabPath,

        [Parameter(
            Position=3,
            ParameterSetName="Lab",
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab
    ) # Param

    begin
    {
        # Remove some PSBoundParameters so we can Splat
        if ($PSCmdlet.ParameterSetName -eq 'File')
        {
            # Read the configuration
            $Lab = Get-Lab `
                @PSBoundParameters
        } # if
    } # begin

    process
    {
        # Get the VMs
        $vms = Get-LabVM `
            -Lab $Lab

        # Get the bootorders by highest first and ignoring 0
        $bootOrders = @( ($vms |
            Where-Object -FilterScript { ($_.Bootorder -gt 0) } ).Bootorder )
        $bootPhases = @( ($bootOrders | Sort-Object -Unique -Descending) )

        # Step through each of these "Bootphases" waiting for them to complete
        foreach ($bootPhase in $bootPhases)
        {
            # Process this "Bootphase"
            Write-LabMessage -Message $($LocalizedData.StoppingBootPhaseVMsMessage `
                -f $bootPhase)

            # Get all VMs in this "Bootphase"
            $bootVMs = @( $vms |
                Where-Object -FilterScript { ($_.BootOrder -eq $bootPhase) } )

            $phaseComplete = $false
            $phaseAllStopped = $true
            $vmCount = $bootVMs.Count
            $vmNumber = 0

            # Loop through all the VMs in this "Bootphase" repeatedly
            while (-not $phaseComplete)
            {
                # Get the VM to boot/check
                $VM = $bootVMs[$vmNumber]
                $vmName = $VM.Name

                # Get the actual Hyper-V VM object
                $vmObject = Get-VM `
                    -Name $vmName `
                    -ErrorAction SilentlyContinue

                if (-not $vmObject)
                {
                    # if the VM does not exist then throw a non-terminating exception
                    $exceptionParameters = @{
                        errorId = 'VMDoesNotExistError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDoesNotExistError `
                            -f $vmName)

                    }
                    New-LabException @exceptionParameters
                } # if

                # Shutodwn the VM if it is off
                if ($vmObject.State -eq 'Running')
                {
                    Write-LabMessage -Message $($LocalizedData.StoppingVMMessage `
                        -f $VMName)
                    $null = Stop-VM `
                        -VM $vmObject `
                        -Force `
                        -ErrorAction Continue
                } # if

                # Determine if the VM has stopped.
                if ($vmObject -and (Get-VM -VMName $vmName).State -ne 'Off')
                {
                    # It has not stopped
                    $phaseAllStopped = $false
                } # if

                $vmNumber++

                if ($vmNumber -eq $vmCount)
                {
                    <#
                        We have stepped through all VMs in this Phase so check
                        if all have stopped, otherwise reset the loop.
                    #>

                    if ($phaseAllStopped)
                    {
                        <#
                            if we have gone through all VMs in this "Bootphase"
                            and they're all marked as stopped then we can mark
                            this phase as complete and allow moving on to the next one
                        #>

                        Write-LabMessage -Message $($LocalizedData.AllBootPhaseVMsStoppedMessage `
                            -f $bootPhase)
                        $phaseComplete = $true
                    }
                    else
                    {
                        $phaseAllStopped = $true
                    } # if

                    # Reset the VM Loop
                    $vmNumber = 0
                } # if
            } # while
        } # foreach

        Write-LabMessage -Message $($LocalizedData.LabStopCompleteMessage `
            -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.fullconfigpath)
    } # process

    end
    {
    } # end
} # Stop-Lab
#EndRegion './Public/Stop-Lab.ps1' 147
#Region './Public/Uninstall-Lab.ps1' 0
function Uninstall-Lab
{
    [CmdLetBinding(DefaultParameterSetName="Lab",
        SupportsShouldProcess = $true,
        ConfirmImpact = 'High')]
    param
    (
        [parameter(
            Position=1,
            ParameterSetName="File",
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [parameter(
            Position=2,
            ParameterSetName="File")]
        [ValidateNotNullOrEmpty()]
        [System.String] $LabPath,

        [Parameter(
            Position=3,
            ParameterSetName="Lab",
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=4)]
        [Switch] $RemoveSwitch,

        [Parameter(
            Position=5)]
        [Switch] $RemoveVMTemplate,

        [Parameter(
            Position=6)]
        [Switch] $RemoveVMFolder,

        [Parameter(
            Position=7)]
        [Switch] $RemoveVMTemplateVHD,

        [Parameter(
            Position=8)]
        [Switch] $RemoveLabFolder
    ) # Param

    begin
    {
        # Remove some PSBoundParameters so we can Splat
        $null = $PSBoundParameters.Remove('RemoveSwitch')
        $null = $PSBoundParameters.Remove('RemoveVMTemplate')
        $null = $PSBoundParameters.Remove('RemoveVMFolder')
        $null = $PSBoundParameters.Remove('RemoveVMTemplateVHD')
        $null = $PSBoundParameters.Remove('RemoveLabFolder')

        if ($PSCmdlet.ParameterSetName -eq 'File')
        {
            # Read the configuration
            $Lab = Get-Lab `
                @PSBoundParameters
        } # if
    } # begin

    process
    {
        if ($PSCmdlet.ShouldProcess( 'LocalHost', `
            ($LocalizedData.ShouldUninstallLab `
            -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )))
        {
            # Remove the VMs
            $VMSplat = @{}
            if ($RemoveVMFolder)
            {
                $VMSplat += @{ RemoveVMFolder = $true }
            } # if
            $null = Remove-LabVM `
                -Lab $Lab `
                @VMSplat

            # Remove the VM Templates
            if ($RemoveVMTemplate)
            {
                if ($PSCmdlet.ShouldProcess( 'LocalHost', `
                    ($LocalizedData.ShouldRemoveVMTemplate `
                    -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )))
                {
                    $null = Remove-LabVMTemplate `
                        -Lab $Lab
                } # if
            } # if

            # Remove the VM Switches
            if ($RemoveSwitch)
            {
                if ($PSCmdlet.ShouldProcess( 'LocalHost', `
                    ($LocalizedData.ShouldRemoveSwitch `
                    -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )))
                {
                    $null = Remove-LabSwitch `
                        -Lab $Lab
                } # if
            } # if

            # Remove the VM Template VHDs
            if ($RemoveVMTemplateVHD)
            {
                if ($PSCmdlet.ShouldProcess( 'LocalHost', `
                    ($LocalizedData.ShouldRemoveVMTemplateVHD `
                    -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )))
                {
                    $null = Remove-LabVMTemplateVHD `
                        -Lab $Lab
                } # if
            } # if

            # Remove the Lab Folder
            if ($RemoveLabFolder)
            {
                if (Test-Path -Path $Lab.labbuilderconfig.settings.labpath)
                {
                    if ($PSCmdlet.ShouldProcess( 'LocalHost', `
                        ($LocalizedData.ShouldRemoveLabFolder `
                        -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )))
                    {
                        Remove-Item `
                            -Path $Lab.labbuilderconfig.settings.labpath `
                            -Recurse `
                            -Force
                    } # if
                } # if
            } # if

            # Remove the LabBuilder Management Network switch
            [System.String] $ManagementSwitchName = Get-LabManagementSwitchName `
                -Lab $Lab
            if ((Get-VMSwitch | Where-Object -Property Name -eq $ManagementSwitchName).Count -ne 0)
            {
                $null = Remove-VMSwitch `
                    -Name $ManagementSwitchName

                Write-LabMessage -Message $($LocalizedData.RemovingLabManagementSwitchMessage `
                    -f $ManagementSwitchName)
            }

            Write-LabMessage -Message $($LocalizedData.LabUninstallCompleteMessage `
                -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.labpath )
        } # if
    } # process

    end
    {
    } # end
} # Uninstall-Lab
#EndRegion './Public/Uninstall-Lab.ps1' 157
#Region './Public/Update-Lab.ps1' 0
function Update-Lab
{
    [CmdLetBinding(DefaultParameterSetName="Lab")]
    param
    (
        [parameter(
            Position=1,
            ParameterSetName="File",
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ConfigPath,

        [parameter(
            Position=2,
            ParameterSetName="File")]
        [ValidateNotNullOrEmpty()]
        [System.String] $LabPath,

        [Parameter(
            Position=3,
            ParameterSetName="Lab",
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab
    ) # Param

    begin
    {
        if ($PSCmdlet.ParameterSetName -eq 'File')
        {
            # Read the configuration
            $Lab = Get-Lab `
                @PSBoundParameters
        } # if
    } # begin

    process
    {
        Install-Lab `
            @PSBoundParameters

        Write-LabMessage -Message $($LocalizedData.LabUpdateCompleteMessage `
            -f $Lab.labbuilderconfig.name,$Lab.labbuilderconfig.settings.fullconfigpath)
    } # process

    end
    {
    } # end
} # Update-Lab
#EndRegion './Public/Update-Lab.ps1' 51
#Region './suffix.ps1' 0
#EndRegion './suffix.ps1' 1