lib/public/vm.ps1

<#
.SYNOPSIS
    Gets an Array of LabVM objects from a Lab.
.DESCRIPTION
    Takes the provided Lab and returns the list of VM objects that will be created in this lab.
    This list is usually passed to Initialize-LabVM.
.PARAMETER Lab
    Contains the Lab Builder Lab object that was loaded by the Get-Lab object.
.PARAMETER Name
    An optional array of VM names.
 
    Only VMs matching names in this list will be returned in the array.
.PARAMETER VMTemplates
    Contains the array of LabVMTemplate objects returned by Get-LabVMTemplate from this Lab.
 
    If not provided it will attempt to pull the list from the Lab.
.PARAMETER Switches
    Contains the array of LabVMSwitch objects returned by Get-LabSwitch from this Lab.
 
    If not provided it will attempt to pull the list from the Lab.
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    $VMTemplates = Get-LabVMTemplate -Lab $Lab
    $Switches = Get-LabSwitch -Lab $Lab
    $VMs = Get-LabVM `
        -Lab $Lab `
        -VMTemplates $VMTemplates `
        -Switches $Switches
    Loads a Lab and pulls the array of VMs from it.
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    $VMs = Get-LabVM -Lab $Lab
    Loads a Lab and pulls the array of VMs from it.
.OUTPUTS
    Returns an array of LabVM objects.
#>

function Get-LabVM {
    [OutputType([LabVM[]])]
    [CmdLetBinding()]
    param (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [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 = @()
    [String] $LabPath = $Lab.labbuilderconfig.settings.labpath
    [String] $VHDParentPath = $Lab.labbuilderconfig.settings.vhdparentpathfull
    [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)
            }
            ThrowException @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)
                }
                ThrowException @ExceptionParameters
            } # if

            # Find the template that this VM uses and get the VHD Path
            [String] $ParentVHDPath = ''
            [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)
                }
                ThrowException @ExceptionParameters
            } # if

            # Get path to Offline Domain Join file if it exists
            [String]$NanoODJPath = $null
            if ($VM.NanoODJPath)
            {
                $NanoODJPath = $VM.NanoODJPath
            } # if

            # Assemble the Network adapters that this VM will use
            [LabVMAdapter[]] $VMAdapters = @()
            [Int] $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)
                    }
                    ThrowException @ExceptionParameters
                } # if

                if (-not $AdapterSwitchName)
                {
                    $ExceptionParameters = @{
                        errorId = 'VMAdapterSwitchNameError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMAdapterSwitchNameError `
                            -f $VMName,$AdapterName)
                    }
                    ThrowException @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
                [Boolean] $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)
                    }
                    ThrowException @ExceptionParameters
                } # if

                # Figure out the VLan - If defined in the VM use it, otherwise use the one defined in the Switch, otherwise keep blank.
                [String] $VLan = $VMAdapter.VLan
                if (-not $VLan)
                {
                    $VLan = $SwitchVLan
                } # if

                [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(`
                            (IncreaseIpAddress `
                                -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(`
                            (IncreaseIpAddress `
                                -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 = IncreaseMacAddress `
                        -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 = @()
            [Int] $DataVhdCount = 0
            foreach ($VMDataVhd in $VM.DataVhds.DataVhd)
            {
                $DataVhdCount++

                # Load all the VHD properties and check they are valid
                [String] $Vhd = $VMDataVhd.Vhd
                if (-not $VMDataVhd.Vhd)
                {
                    $ExceptionParameters = @{
                        errorId = 'VMDataDiskVHDEmptyError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.VMDataDiskVHDEmptyError `
                            -f $VMName)
                    }
                    ThrowException @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)
                        }
                        ThrowException @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)
                        }
                        ThrowException @ExceptionParameters
                    } # if
                } # if

                # Get the disk size if provided
                if ($VMDataVhd.Size)
                {
                    $NewDataVHD.Size = (Invoke-Expression $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)
                                }
                                ThrowException @ExceptionParameters
                            } # if
                            if ($NewDataVHD.Shared)
                            {
                                $ExceptionParameters = @{
                                    errorId = 'VMDataDiskSharedDifferencingError'
                                    errorCategory = 'InvalidArgument'
                                    errorMessage = $($LocalizedData.VMDataDiskSharedDifferencingError `
                                        -f $VMName,$VHD)
                                }
                                ThrowException @ExceptionParameters
                            } # if
                        }
                        Default
                        {
                            $ExceptionParameters = @{
                                errorId = 'VMDataDiskUnknownTypeError'
                                errorCategory = 'InvalidArgument'
                                errorMessage = $($LocalizedData.VMDataDiskUnknownTypeError `
                                    -f $VMName,$VHD,$VMDataVhd.Type)
                            }
                            ThrowException @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)
                        }
                        ThrowException @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)
                        }
                        ThrowException @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)
                        }
                        ThrowException @ExceptionParameters
                    } # if
                    if (-not $NewDataVHD.FileSystem)
                    {
                        $ExceptionParameters = @{
                            errorId = 'VMDataDiskFileSystemMissingError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.VMDataDiskFileSystemMissingError `
                                -f $VMName,$VHD)
                        }
                        ThrowException @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)
                            }
                        ThrowException @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)
                        }
                        ThrowException @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)
                    }
                    ThrowException @ExceptionParameters
                } # if

                $DataVHDs += @( $NewDataVHD )
            } # foreach

            # Assemble the DVD Drives this VM will use
            [LabDVDDrive[]] $DVDDrives = @()
            [Int] $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)
                        }
                        ThrowException @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?
            [String] $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)
                    }
                    ThrowException @ExceptionParameters
                } # if
            } # if

            # Does the VM specify a Setup Complete Script?
            [String] $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)
                    }
                    ThrowException @ExceptionParameters
                } # if
                if (-not (Test-Path $SetupComplete))
                {
                    $ExceptionParameters = @{
                        errorId = 'SetupCompleteFileMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.SetupCompleteFileMissingError `
                            -f $VMName,$SetupComplete)
                    }
                    ThrowException @ExceptionParameters
                } # if
            } # if

            # Create the Lab DSC object
            $LabDSC = [LabDSC]::New($VM.DSC.ConfigName)

            # Load the DSC Config File setting and check it
            [String] $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)
                    }
                    ThrowException @ExceptionParameters
                } # if

                if (-not (Test-Path $LabDSC.ConfigFile))
                {
                    $ExceptionParameters = @{
                        errorId = 'DSCConfigFileMissingError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.DSCConfigFileMissingError `
                            -f $VMName,$LabDSC.ConfigFile)
                    }
                    ThrowException @ExceptionParameters
                } # if
                if (-not $VM.DSC.ConfigName)
                {
                    $ExceptionParameters = @{
                        errorId = 'DSCConfigNameIsEmptyError'
                        errorCategory = 'InvalidArgument'
                        errorMessage = $($LocalizedData.DSCConfigNameIsEmptyError `
                            -f $VMName)
                    }
                    ThrowException @ExceptionParameters
                } # if
            } # if

            # Load the DSC Parameters
            [String] $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
            [Boolean] $LabDSC.Logging = ($VM.DSC.Logging -eq 'Y')

            # Get the Memory Startup Bytes (from the template or VM)
            [Int64] $MemoryStartupBytes = 1GB
            if ($VM.memorystartupbytes)
            {
                $MemoryStartupBytes = (Invoke-Expression $VM.memorystartupbytes)
            }
            elseif ($VMTemplate.memorystartupbytes)
            {
                $MemoryStartupBytes = $VMTemplate.memorystartupbytes
            } # if

            # Get the Dynamic Memory Enabled flag
            [Boolean] $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)
            [Int] $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)
                }
                ThrowException @ExceptionParameters
            } # if

            [Boolean] $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)
            [String] $AdministratorPassword = ''
            if ($VM.administratorpassword)
            {
                $AdministratorPassword = $VM.administratorpassword
            }
            elseif ($VMTemplate.administratorpassword)
            {
                $AdministratorPassword = $VMTemplate.administratorpassword
            } # if

            # Get the Product Key (from the template or VM)
            [String] $ProductKey = ''
            if ($VM.productkey)
            {
                $ProductKey = $VM.productkey
            }
            elseif ($VMTemplate.productkey)
            {
                $ProductKey = $VMTemplate.productkey
            } # if

            # Get the Timezone (from the template or VM)
            [String] $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
            [String] $Packages = $null
            if ($VM.packages)
            {
                $Packages = $VM.packages
            }
            elseif ($VMTemplate.packages)
            {
                $Packages = $VMTemplate.packages
            } # if

            # Get the Version (from the template or VM)
            [String] $Version = '8.0'
            if ($VM.version)
            {
                $Version = $VM.version
            }
            elseif ($VMTemplate.version)
            {
                $Version = $VMTemplate.version
            } # if

            # Get the Generation (from the template or VM)
            [String] $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


<#
.SYNOPSIS
    Initializes the Virtual Machines used by a Lab from a provided array.
.DESCRIPTION
    Takes an array of LabVM objects that were configured in the Lab.
.PARAMETER Lab
    Contains the Lab object that was loaded by the Get-Lab object.
.PARAMETER Name
    An optional array of VM names.
 
    Only VMs matching names in this list will be initialized.
.PARAMETER VMs
    An array of LabVM objects pulled from a Lab object.
 
    If not provided it will attempt to pull the list from the Lab object.
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    $VMs = Get-LabVs -Lab $Lab
    Initialize-LabVM `
        -Lab $Lab `
        -VMs $VMs
    Initializes the Virtual Machines in the configured in the Lab c:\mylab\config.xml
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    Initialize-LabVMs -Lab $Lab
    Initializes the Virtual Machines in the configured in the Lab c:\mylab\config.xml
.OUTPUTS
    None.
#>

function Initialize-LabVM {
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [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

    [String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # Figure out the name of the LabBuilder control switch
    $ManagementSwitchName = GetManagementSwitchName `
        -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
        [String] $VMRootPath = $VM.VMRootPath

        # Get the Virtual Machine Path
        [String] $VMPath = Join-Path `
            -Path $VMRootPath `
            -ChildPath 'Virtual Machines'

        # Get the Virtual Hard Disk Path
        [String] $VHDPath = Join-Path `
            -Path $VMRootPath `
            -ChildPath 'Virtual Hard Disks'

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

        if (($CurrentVMs | Where-Object -Property Name -eq $VM.Name).Count -eq 0)
        {
            WriteMessage -Message $($LocalizedData.CreatingVMMessage `
                -f $VM.Name)

            # Make sure the appropriate folders exist
            InitializeVMPaths `
                -VMPath $VMRootPath

            # Create the boot disk
            $VMBootDiskPath = "$VHDPath\$($VM.Name) Boot Disk.vhdx"
            if (-not (Test-Path -Path $VMBootDiskPath))
            {
                if ($VM.UseDifferencingDisk)
                {
                    WriteMessage -Message $($LocalizedData.CreatingVMDiskMessage `
                        -f $VM.Name,$VMBootDiskPath,'Differencing Boot')

                    $Null = New-VHD `
                        -Differencing `
                        -Path $VMBootDiskPath `
                        -ParentPath $VM.ParentVHD
                }
                else
                {
                    WriteMessage -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
                CreateVMInitializationFiles `
                    -Lab $Lab `
                    -VM $VM

                # Because this is a new boot disk apply any required initialization
                InitializeBootVHD `
                    -Lab $Lab `
                    -VM $VM `
                    -VMBootDiskPath $VMBootDiskPath
            }
            else
            {
                WriteMessage -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
        if ($VM.DynamicMemoryEnabled -ne (Get-VMMemory -VMName $VM.Name).DynamicMemoryEnabled)
        {
            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)
                }
                ThrowException @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
        UpdateVMIntegrationServices `
            -VM $VM

        # Update the data disks for the VM
        UpdateVMDataDisks `
            -Lab $Lab `
            -VM $VM

        # Update the DVD Drives for the VM
        UpdateVMDVDDrives `
            -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)
        {
            WriteMessage -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

        WriteMessage -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)
            {
                WriteMessage -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

                WriteMessage -Message $($LocalizedData.SettingVMNetworkAdapterVlanMessage `
                    -f $VM.Name,$VMAdapter.Name,'',$VMAdapter.VLan)
            }
            else
            {
                $null = $VMNetworkAdapter |
                    Set-VMNetworkAdapterVlan `
                        -Untagged

                WriteMessage -Message $($LocalizedData.ClearingVMNetworkAdapterVlanMessage `
                    -f $VM.Name,$VMAdapter.Name,'')
            } # if

            if ([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


<#
.SYNOPSIS
    Removes all Lab Virtual Machines.
.DESCRIPTION
    This cmdlet is used to remove any Virtual Machines that were created as part of this
    Lab.
 
    It can also optionally delete the folder and all files created as part of this Lab
    Virutal Machine.
.PARAMETER Lab
    Contains the Lab object that was loaded by the Get-Lab object.
.PARAMETER Name
    An optional array of VM names.
 
    Only VMs matching names in this list will be removed.
.PARAMETER VMs
    The array of LabVM objects pulled from the Lab using Get-LabVM.
 
    If not provided it will attempt to pull the list from the Lab object.
.PARAMETER RemoveVMFolder
    Causes the folder created to contain the Virtual Machine in this lab to be deleted.
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    $VMTemplates = Get-LabVMTemplate -Lab $Lab
    $VMs = Get-LabVs -Lab $Lab -VMTemplates $VMTemplates
    Remove-LabVM -Lab $Lab -VMs $VMs
    Removes any Virtual Machines configured in the Lab c:\mylab\config.xml
.EXAMPLE
    $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
    Remove-LabVM -Lab $Lab
    Removes any Virtual Machines configured in the Lab c:\mylab\config.xml
.OUTPUTS
    None.
#>

function Remove-LabVM {
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [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
    [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')
            {
                WriteMessage -Message $($LocalizedData.StoppingVMMessage `
                    -f $VM.Name)

                Stop-VM `
                    -Name $VM.Name
                # Wait for it to completely shut down and report that it is off.
                WaitVMOff `
                    -VM $VM
            }

            WriteMessage -Message $($LocalizedData.RemovingVMMessage `
                -f $VM.Name)

            # Now delete the actual VM
            Get-VM `
                -Name $VM.Name | Remove-VM -Force -Confirm:$False

            WriteMessage -Message $($LocalizedData.RemovedVMMessage `
                -f $VM.Name)
        }
        else
        {
            WriteMessage -Message $($LocalizedData.VMNotFoundMessage `
                -f $VM.Name)
        }
    }
    # Should we remove the VM Folder?
    if ($RemoveVMFolder)
    {
        if (Test-Path -Path $VM.VMRootPath)
        {
            WriteMessage -Message $($LocalizedData.DeletingVMFolderMessage `
                -f $VM.Name)

            Remove-Item `
                -Path $VM.VMRootPath `
                -Recurse `
                -Force
        }
    }
} # Remove-LabVM



<#
.SYNOPSIS
   Starts a Lab VM and ensures it has been Initialized.
.DESCRIPTION
   This cmdlet is used to start up a Lab VM for the first time.
 
   It will start the VM if it is off.
 
   If the VM is a Server OS or Nano Server then it will also perform an initial setup:
    - It will ensure that initial setup has been completed and a self-signed certificate has
      been created by the VM and downloaded to the LabBuilder folder.
 
    - It will also ensure DSC is configured for the VM.
.PARAMETER VM
   The LabVM Object referring to the VM to start to.
.EXAMPLE
   $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
   $VMs = Get-LabVM -Lab $Lab
   $Session = Install-LabVM -VM $VMs[0]
   Start up the first VM in the Lab c:\mylab\config.xml and initialize it.
.OUTPUTS
   None.
#>

function Install-LabVM {
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $Lab,

        [Parameter(
            Position=2)]
        [ValidateNotNullOrEmpty()]
        [LabVM] $VM
    )

    [String] $LabPath = $Lab.labbuilderconfig.settings.labpath

    # The VM is now ready to be started
    if ((Get-VM -Name $VM.Name).State -eq 'Off')
    {
        WriteMessage -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.OSType -in ([LabOStype]::Server,[LabOStype]::Nano))
    {
        # 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 (WaitVMInitializationComplete -VM $VM -ErrorAction Continue)
            {
                WriteMessage -Message $($LocalizedData.CertificateDownloadStartedMessage `
                    -f $VM.Name)

                if ($VM.CertificateSource -eq [LabCertificateSource]::Guest)
                {
                    if (Recieve-SelfSignedCertificate -Lab $Lab -VM $VM)
                    {
                        WriteMessage -Message $($LocalizedData.CertificateDownloadCompleteMessage `
                            -f $VM.Name)
                    }
                    else
                    {
                        $ExceptionParameters = @{
                            errorId = 'CertificateDownloadError'
                            errorCategory = 'InvalidArgument'
                            errorMessage = $($LocalizedData.CertificateDownloadError `
                                -f $VM.name)
                        }
                        ThrowException @ExceptionParameters
                    } # if
                } # if
            }
            else
            {
                $ExceptionParameters = @{
                    errorId = 'InitializationDidNotCompleteError'
                    errorCategory = 'InvalidArgument'
                    errorMessage = $($LocalizedData.InitializationDidNotCompleteError `
                        -f $VM.name)
                }
                ThrowException @ExceptionParameters
            } # if
        } # if

        if ($VM.OSType -in ([LabOStype]::Nano))
        {
        # Copy ODJ Files if it Exists
            CopyODJ `
                -Lab $Lab `
                -VM $VM
        } # if

        # Create any DSC Files for the VM
        InitializeDSC `
            -Lab $Lab `
            -VM $VM

        # Attempt to start DSC on the VM
        StartDSC `
            -Lab $Lab `
            -VM $VM
    } # if
} # Install-LabVM



<#
.SYNOPSIS
   Connects to a running Lab VM.
.DESCRIPTION
   This cmdlet will connect to a running VM using PSRemoting. A PSSession object will be returned
   if the connection was successful.
 
   If the connection fails, it will be retried until the ConnectTimeout is reached. If the
   ConnectTimeout is reached and a connection has not been established then a ConnectionError
   exception will be thrown.
 
   The IP Address to this VM will be added to the WSMan TrustedHosts list if it isn't already
   added or if it isn't set to '*'.
.PARAMETER VM
   The LabVM Object referring to the VM to connect to.
.PARAMETER ConnectTimeout
   The number of seconds the connection will attempt to be established for.
 
   Defaults to 300 seconds.
.EXAMPLE
   $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
   $VMs = Get-LabVM -Lab $Lab
   $Session = Connect-LabVM -VM $VMs[0]
   Connect to the first VM in the Lab c:\mylab\config.xml.
.OUTPUTS
   The PSSession object of the remote connect or null if the connection failed.
#>

function Connect-LabVM
{
    [OutputType([System.Management.Automation.Runspaces.PSSession])]
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [LabVM] $VM,

        [Parameter(
            Position=2)]
        [Int] $ConnectTimeout = 300
    )

    [DateTime] $StartTime = Get-Date
    [System.Management.Automation.Runspaces.PSSession] $Session = $null
    [PSCredential] $AdminCredential = CreateCredential `
        -Username '.\Administrator' `
        -Password $VM.AdministratorPassword
    [Boolean] $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 = GetVMManagementIPAddress `
                -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 ([String]::IsNullOrWhitespace($TrustedHosts))
                {
                    $TrustedHosts = $IPAddress
                }
                else
                {
                    $TrustedHosts = "$TrustedHosts,$IPAddress"
                }
                Set-Item `
                    -Path WSMAN::localhost\Client\TrustedHosts `
                    -Value $TrustedHosts `
                    -Force
                WriteMessage -Message $($LocalizedData.AddingIPAddressToTrustedHostsMessage `
                    -f $VM.Name,$IPAddress)
            }

            WriteMessage -Message $($LocalizedData.ConnectingVMMessage `
                -f $VM.Name,$IPAddress)

            $Session = New-PSSession `
                -Name 'LabBuilder' `
                -ComputerName $IPAddress `
                -Credential $AdminCredential `
                -ErrorAction Stop
        }
        catch
        {
            if (-not $IPAddress)
            {
                WriteMessage -Message $($LocalizedData.WaitingForIPAddressAssignedMessage `
                    -f $VM.Name,$Script:RetryConnectSeconds)
            }
            else
            {
                WriteMessage -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)
        }
        ThrowException @ExceptionParameters
    }
    Return $Session
} # Connect-LabVM


<#
.SYNOPSIS
   Disconnects from a running Lab VM.
.DESCRIPTION
   This cmdlet will disconnect a session from a running VM using PSRemoting.
 
   The IP Address to this VM will be removed from the WSMan TrustedHosts list
   if it exists in it.
.PARAMETER VM
   The LabVM Object referring to the VM to disconnect from.
.EXAMPLE
   $Lab = Get-Lab -ConfigPath c:\mylab\config.xml
   $VMs = Get-LabVM -Lab $Lab
   Disconnect-LabVM -VM $VMs[0]
   Disconnect from the first VM in the Lab c:\mylab\config.xml.
.OUTPUTS
   None
#>

function Disconnect-LabVM
{
    [CmdLetBinding()]
    param
    (
        [Parameter(
            Position=1,
            Mandatory=$true)]
        [LabVM] $VM
    )

    [PSCredential] $AdminCredential = CreateCredential `
        -Username '.\Administrator' `
        -Password $VM.AdministratorPassword

    # Get the Management IP Address of the VM
    $IPAddress = GetVMManagementIPAddress `
        -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.
            WriteMessage -Message $($LocalizedData.VMSessionDoesNotExistMessage `
                -f $VM.Name)
        }
        else
        {
            if ($Session.State -eq 'Opened')
            {
                # Disconnect the session
                $null = $Session | Disconnect-PSSession
                WriteMessage -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 { $_ -ne $IPAddress }) -join ','
            Set-Item `
                -Path WSMAN::localhost\Client\TrustedHosts `
                -Value $TrustedHosts `
                -Force
            WriteMessage -Message $($LocalizedData.RemovingIPAddressFromTrustedHostsMessage `
                -f $VM.Name,$IPAddress)
        }
    } # try
} # Disconnect-LabVM