AutomatedLabAzureWorkerVirtualMachines.psm1

$PSDefaultParameterValues = @{
    '*-Azure*:Verbose' = $false
    '*-Azure*:Warning' = $false
    'Import-Module:Verbose' = $false
}

#region New-LWAzureVM
function New-LWAzureVM
{
    [Cmdletbinding()]
    Param (
        [Parameter(Mandatory)]
        [AutomatedLab.Machine]$Machine
    )
    
    Write-LogFunctionEntry
    
    $lab = Get-Lab
    
    $resourceGroupName = $lab.Name
    if ($machine.AzureProperties)
    {
        if ($machine.AzureProperties.ContainsKey('ResourceGroupName'))
        {
            #if the resource group name is provided for the machine, it replaces the default
            $resourceGroupName = $machine.AzureProperties.ResourceGroupName
        }
    }
    
    $machineResourceGroup = $Machine.AzureProperties.ResourceGroupName
    if (-not $machineResourceGroup)
    {
        $machineResourceGroup = (Get-LabAzureDefaultResourceGroup).ResourceGroupName
    }
    Write-Verbose -Message "Target resource group for machine: '$machineResourceGroup'"
    
    if (-not $global:cacheVMs)
    {
        $global:cacheVMs = Get-AzureRmVM -WarningAction SilentlyContinue
    }

    if ($global:cacheVMs | Where-Object {$_.Name -eq $Machine.Name -and $_.ResourceGroupName -eq $resourceGroupName})
    {
        Write-ProgressIndicatorEnd
        Write-ScreenInfo -Message "Machine '$($machine.name)' already exist. Skipping creation of this machine" -Type Warning
        Return $false
    }

    Write-Verbose -Message "Creating container 'automatedlabdisks' for additional disks"
    $storageContext = (Get-AzureRmStorageAccount -Name $lab.AzureSettings.DefaultStorageAccount -ResourceGroupName $machineResourceGroup).Context
    $container = Get-AzureStorageContainer -Name automatedlabdisks -Context $storageContext -ErrorAction SilentlyContinue
    if (-not $container)
    {
        $container = New-AzureStorageContainer -Name automatedlabdisks -Context $storageContext
    }

    Write-Verbose -Message "Scheduling creation Azure machine '$Machine'"

    #random number in the path to prevent conflicts
    $rnd = (Get-Random -Minimum 1 -Maximum 1000).ToString('0000')
    $osVhdLocation = "$($storageContext.BlobEndpoint)/automatedlab1/$($machine.Name)OsDisk$rnd.vhd"
    $lab.AzureSettings.VmDisks.Add($osVhdLocation)
    Write-Verbose -Message "The location of the VM disk is '$osVhdLocation'"

    $adminUserName = $Machine.InstallationUser.UserName
    $adminPassword = $Machine.InstallationUser.Password

    
    
    #if this machine has a SQL Server role
    if ($Machine.Roles.Name -match 'SQLServer(?<SqlVersion>\d{4})')
    {    
        #get the SQL Server version defined in the role
        $sqlServerRoleName = $Matches[0]
        $sqlServerVersion = $Matches.SqlVersion
    }

    #if this machine has a Visual Studio role
    if ($Machine.Roles.Name -match 'VisualStudio(?<Version>\d{4})')
    {
        $visualStudioRoleName = $Matches[0]        
        $visualStudioVersion = $Matches.Version
    }

    #if this machine has a SharePoint role
    if ($Machine.Roles.Name -match 'SharePoint(?<Version>\d{4})')
    {
        $sharePointRoleName = $Matches[0]
        $sharePointVersion = $Matches.Version
    }
                
    if ($sqlServerRoleName)
    {
        Write-Verbose -Message 'This is going to be a SQL Server VM'
        $pattern = 'SQL(?<SqlVersion>\d{4})(?<SqlIsR2>R2)??(?<SqlServicePack>SP\d)?-(?<OS>WS\d{4}(R2)?)'
                
        #get all SQL images machting the RegEx pattern and then get only the latest one
        $sqlServerImages = $lab.AzureSettings.VmImages |
        Where-Object Offer -Match $pattern | 
        Group-Object -Property Sku, Offer | 
        ForEach-Object {
            $_.Group | Sort-Object -Property PublishedDate -Descending | Select-Object -First 1
        }

        #add the version, SP Level and OS from the ImageFamily field to the image object
        foreach ($sqlServerImage in $sqlServerImages)
        {
            $sqlServerImage.Offer -match $pattern | Out-Null

            $sqlServerImage | Add-Member -Name SqlVersion -Value $Matches.SqlVersion -MemberType NoteProperty -Force
            $sqlServerImage | Add-Member -Name SqlIsR2 -Value $Matches.SqlIsR2 -MemberType NoteProperty -Force
            $sqlServerImage | Add-Member -Name SqlServicePack -Value $Matches.SqlServicePack -MemberType NoteProperty -Force
    
            $sqlServerImage | Add-Member -Name OS -Value (New-Object AutomatedLab.OperatingSystem($Matches.OS)) -MemberType NoteProperty -Force
        }

        #get the image that matches the OS and SQL server version
        $machineOs = New-Object AutomatedLab.OperatingSystem($machine.OperatingSystem)
        $vmImage = $sqlServerImages | Where-Object { $_.SqlVersion -eq $sqlServerVersion -and $_.OS.Version -eq $machineOs.Version } |
        Sort-Object -Property SqlServicePack -Descending | Select-Object -First 1
        $offerName = $vmImageName = $vmImage | Select-Object -ExpandProperty Offer
        $publisherName = $vmImage | Select-Object -ExpandProperty PublisherName
        $skusName = $vmImage | Select-Object -ExpandProperty Skus

        if (-not $vmImageName)
        {
            Write-Warning 'SQL Server image could not be found. The following combinations are currently supported by Azure:'
            foreach ($sqlServerImage in $sqlServerImages)
            {
                Write-Host $sqlServerImage.Label
            }

            throw "There is no Azure VM image for '$sqlServerRoleName' on operating system '$($machine.OS)'. The machine cannot be created. Cancelling lab setup. Please find the available images above."
        }
    }
    elseif ($visualStudioRoleName)
    {
        Write-Verbose -Message 'This is going to be a Visual Studio VM'

        $pattern = 'VS-(?<Version>\d{4})-(?<Edition>\w+)-VSU(?<Update>\d)-AzureSDK-\d{2,3}-((?<OS>WIN\d{2})|(?<OS>WS\d{4,6}))'
                
        #get all SQL images machting the RegEx pattern and then get only the latest one
        $visualStudioImages = $lab.AzureSettings.VmImages |
        Where-Object Offer -EQ VisualStudio

        #add the version, SP Level and OS from the ImageFamily field to the image object
        foreach ($visualStudioImage in $visualStudioImages)
        {
            $visualStudioImage.Skus -match $pattern | Out-Null

            $visualStudioImage | Add-Member -Name Version -Value $Matches.Version -MemberType NoteProperty -Force
            $visualStudioImage | Add-Member -Name Update -Value $Matches.Update -MemberType NoteProperty -Force
    
            $visualStudioImage | Add-Member -Name OS -Value (New-Object AutomatedLab.OperatingSystem($Matches.OS)) -MemberType NoteProperty -Force
        }

        #get the image that matches the OS and SQL server version
        $machineOs = New-Object AutomatedLab.OperatingSystem($machine.OperatingSystem)
        $vmImage = $visualStudioImages | Where-Object { $_.Version -eq $visualStudioVersion -and $_.OS.Version.Major -eq $machineOs.Version.Major } |
        Sort-Object -Property Update -Descending | Select-Object -First 1
        $offerName = $vmImageName = $vmImage | Select-Object -ExpandProperty Offer
        $publisherName = $vmImage | Select-Object -ExpandProperty PublisherName
        $skusName = $vmImage | Select-Object -ExpandProperty Skus

        if (-not $vmImageName)
        {
            Write-Warning 'Visual Studio image could not be found. The following combinations are currently supported by Azure:'
            foreach ($visualStudioImage in $visualStudioImages)
            {
                Write-Host $visualStudioImage.Label
            }

            throw "There is no Azure VM image for '$visualStudioRoleName' on operating system '$($machine.OperatingSystem)'. The machine cannot be created. Cancelling lab setup. Please find the available images above."
        }
    }
    elseif ($sharePointRoleName)
    {
        Write-Verbose -Message 'This is going to be a SharePoint VM'

        # AzureRM currently has only one SharePoint offer
        
        $sharePointImages = $lab.AzureSettings.VmImages |
        Where-Object Offer -Match 'SharePoint' |
        Sort-Object -Property PublishedDate -Descending | Select-Object -First 1

        # Add the SP version
        foreach ($sharePointImage in $sharePointImages)
        {
            $sharePointImage | Add-Member -Name Version -Value $sharePointImage.Skus -MemberType NoteProperty -Force
        }

        #get the image that matches the OS and SQL server version
        $machineOs = New-Object AutomatedLab.OperatingSystem($machine.OperatingSystem)
        Write-Warning "The SharePoint 2013 Trial image in Azure does not have any information about the OS anymore, hence this operating system specified is ignored. There is only $($sharePointImages.Count) image available."
        
        #$vmImageName = $sharePointImages | Where-Object { $_.Version -eq $sharePointVersion -and $_.OS.Version -eq $machineOs.Version } |
        $vmImage = $sharePointImages | Where-Object Version -eq $sharePointVersion |
        Sort-Object -Property Update -Descending | Select-Object -First 1

        $offerName = $vmImageName = $vmImage | Select-Object -ExpandProperty Offer
        $publisherName = $vmImage | Select-Object -ExpandProperty PublisherName
        $skusName = $vmImage | Select-Object -ExpandProperty Skus

        if (-not $vmImageName)
        {
            Write-Warning 'SharePoint image could not be found. The following combinations are currently supported by Azure:'
            foreach ($sharePointImage in $sharePointImages)
            {
                Write-Host $sharePointImage.Label $sharePointImage.ImageFamily
            }

            throw "There is no Azure VM image for '$sharePointRoleName' on operating system '$($Machine.OperatingSystem)'. The machine cannot be created. Cancelling lab setup. Please find the available images above."
        }
    }
    else
    {
        $vmImageName = (New-Object AutomatedLab.OperatingSystem($machine.OperatingSystem)).AzureImageName
        if (-not $vmImageName)
        {
            throw "There is no Azure VM image for the operating system '$($Machine.OperatingSystem)'. The machine cannot be created. Cancelling lab setup."
        }

        $vmImage = $lab.AzureSettings.VmImages |
        Where-Object Skus -eq $vmImageName  |
        Select-Object -First 1

        $offerName = $vmImageName = $vmImage | Select-Object -ExpandProperty Offer
        $publisherName = $vmImage | Select-Object -ExpandProperty PublisherName
        $skusName = $vmImage | Select-Object -ExpandProperty Skus
    }
    Write-Verbose -Message "We selected the SKUs $skusName from offer $offerName by publisher $publisherName"
    
    Write-ProgressIndicator
    
    if ($machine.AzureProperties.RoleSize)
    {
        $roleSize = $lab.AzureSettings.RoleSizes |
        Where-Object { $_.Name -eq $machine.AzureProperties.RoleSize }
        Write-Verbose -Message "Using specified role size of '$($roleSize.Name)'"
    }
    elseif ($machine.AzureProperties.UseAllRoleSizes)
    {
        $DefaultAzureRoleSize = $MyInvocation.MyCommand.Module.PrivateData.DefaultAzureRoleSize
        $roleSize = $lab.AzureSettings.RoleSizes |
        Where-Object { $_.MemoryInMB -ge $machine.Memory -and $_.NumberOfCores -ge $machine.Processors -and $machine.Disks.Count -le $_.MaxDataDiskCount } |
        Sort-Object -Property MemoryInMB, NumberOfCores |
        Select-Object -First 1

        Write-Verbose -Message "Using specified role size of '$($roleSize.InstanceSize)'. VM was configured to all role sizes but constrained to role size '$DefaultAzureRoleSize' by psd1 file"
    }
    else
    {
        switch ($lab.AzureSettings.DefaultRoleSize)
        {
            'A' { $pattern = '^(Standard_A\d{1,2}|Basic_A\d{1,2})' }
            'D' { $pattern = '^Standard_D\d{1,2}' }
            'DS' { $pattern = '^Standard_DS\d{1,2}' }
            'G' { $pattern = '^Standard_G\d{1,2}' }
            'F' { $pattern = '^Standard_F\d{1,2}' }
            default { $pattern = '^(Standard_A\d{1,2}|Basic_A\d{1,2})'}
        }
        
        $roleSize = $lab.AzureSettings.RoleSizes |
        Where-Object Name -Match $pattern |
        Where-Object { $_.MemoryInMB -ge ($machine.Memory / 1MB) -and $_.NumberOfCores -ge $machine.Processors } |
        Sort-Object -Property MemoryInMB, NumberOfCores |
        Select-Object -First 1

        Write-Verbose -Message "Using specified role size of '$($roleSize.Name)' out of role sizes '$pattern'"
    }
    
    if (-not $roleSize)
    {
        throw "Could not find an appropriate role size in Azure $($machine.Processors) cores and $($machine.Memory) MB of memory"
    }
    
    Write-ProgressIndicator
    
    $labVirtualNetworkDefinition = Get-LabVirtualNetworkDefinition

    # List-serialization issues when passing to job. Disks will be added to a hashtable
    $Disks = @{}
    $Machine.Disks | %{$Disks.Add($_.Name,$_.DiskSize)}

    Start-Job -Name "CreateAzureVM ($machineResourceGroup) ($Machine)" -ArgumentList $Machine,
    $Disks,
    $Machine.NetworkAdapters[0].VirtualSwitch.Name,
    $roleSize.Name,
    $vmImageName,
    $osVhdLocation,
    $adminUserName,
    $adminPassword,
    $machineResourceGroup,
    $labVirtualNetworkDefinition,
    $Machine.NetworkAdapters[0].Ipv4Address.IpAddress,
    $storageContext,
    $resourceGroupName,
    $lab.AzureSettings.DefaultLocation.DisplayName,
    $lab.AzureSettings.AzureProfilePath,
    $lab.AzureSettings.DefaultSubscription.SubscriptionName,
    $lab.Name,
    $publisherName,
    $offerName,
    $skusName `
    -ScriptBlock {
        param
        (
            [object]$Machine, #AutomatedLab.Machine
            [object]$Disks,
            [string]$Vnet,
            [string]$RoleSize,
            [string]$VmImageName,
            [string]$OsVhdLocation,
            [string]$AdminUserName,
            [string]$AdminPassword,
            [string]$MachineResourceGroup,
            [object[]]$LabVirtualNetworkDefinition, #AutomatedLab.VirtualNetwork[]
            [object]$DefaultIpAddress, #AutomatedLab.IPAddress
            [object]$StorageContext,
            [string]$ResourceGroupName,
            [string]$Location,
            [string]$SubscriptionPath,
            [string]$SubscriptionName,
            [string]$LabName,
            [string]$PublisherName,
            [string]$OfferName,
            [string]$SkusName
        )

        $VerbosePreference = 'Continue'
        
        Write-Verbose '-------------------------------------------------------'
        Write-Verbose "Machine: $($Machine.name)"
        Write-Verbose "Vnet: $Vnet"
        Write-Verbose "RoleSize: $RoleSize"
        Write-Verbose "VmImageName: $VmImageName"
        Write-Verbose "OsVhdLocation: $OsVhdLocation"
        Write-Verbose "AdminUserName: $AdminUserName"
        Write-Verbose "AdminPassword: $AdminPassword"
        Write-Verbose "ResourceGroupName: $ResourceGroupName"
        Write-Verbose "StorageAccountName: $($StorageContext.StorageAccountName)"
        Write-Verbose "BlobEndpoint: $($StorageContext.BlobEndpoint)"
        Write-Verbose "DefaultIpAddress: $DefaultIpAddress"
        Write-Verbose "Location: $Location"
        Write-Verbose "Subscription file: $SubscriptionPath"
        Write-Verbose "Subscription name: $SubscriptionName"
        Write-Verbose "Lab name: $LabName"
        Write-Verbose "Publisher: $PublisherName"
        Write-Verbose "Offer: $OfferName"
        Write-Verbose "Skus: $SkusName"
        Write-Verbose '-------------------------------------------------------'
                
        Select-AzureRmProfile -Path $SubscriptionPath
        Set-AzureRmContext -SubscriptionName $SubscriptionName
        
        $VerbosePreference = 'Continue'

        $subnet = (Get-AzureRmVirtualNetwork -ResourceGroupName $ResourceGroupName |
        Where-Object { $_.AddressSpace.AddressPrefixes.Contains($Machine.IpAddress[0].ToString()) })[0] |
        Get-AzureRmVirtualNetworkSubnetConfig
        
        Write-Verbose -Message "Subnet for the VM is '$($subnet.Name)'"
        
        Write-Verbose -Message "Calling 'New-AzureVMConfig'"
                                     
        $securePassword = ConvertTo-SecureString -String $AdminPassword -AsPlainText -Force
        $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($AdminUserName, $securePassword)

        $vm =New-AzureRmVMConfig -VMName $Machine.Name -VMSize $RoleSize -ErrorAction Stop
        $vm = Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $Machine.Name -Credential $cred -ProvisionVMAgent -EnableAutoUpdate -ErrorAction Stop -WinRMHttp
                           
        Write-Verbose "Choosing latest source image for $SkusName in $OfferName"
        $vm = Set-AzureRmVMSourceImage -VM $vm -PublisherName $PublisherName -Offer $OfferName -Skus $SkusName -Version "latest" -ErrorAction Stop

        Write-Verbose -Message "Setting private and dynamic public IP addresses."
        $defaultIPv4Address = $DefaultIpAddress
        $publicIpAddress = New-AzureRmPublicIpAddress -Name "$($Machine.Name.ToLower())pip" -ResourceGroupName $ResourceGroupName -Location $Location -DomainNameLabel "$($LabName.ToLower())-$($Machine.Name.ToLower())" -AllocationMethod Dynamic
        if($publicIpAddress.ProvisioningState -ne 'Succeeded')
        {
            throw "No public IP could be assigned to $($machine.Name). Connections to this machine will not work."
        }

        Write-Verbose -Message "Default IP address is '$DefaultIpAddress'. Public IP is $($publicIpAddress.IpAddress)"
        
        Write-Verbose -Message "Creating new network interface with configured private and public IP and subnet $($subnet.Name)"
        $networkInterface = New-AzureRmNetworkInterface -Name "$($Machine.Name.ToLower())nic0" -ResourceGroupName $ResourceGroupName -Location $Location -Subnet $Subnet -PrivateIpAddress $defaultIPv4Address -PublicIpAddress $publicIpAddress
        
        Write-Verbose -Message 'Adding NIC to VM'
        $vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $networkInterface.Id -ErrorAction Stop
        
                                   
        $DiskName = "$($machine.Name)_os"
        $OSDiskUri = "$($StorageContext.BlobEndpoint)automatedlabdisks/$DiskName.vhd"
        
        Write-Verbose "Adding OS disk to VM with blob url $OSDiskUri"
        $vm = Set-AzureRmVMOSDisk -VM $vm -Name $DiskName -VhdUri $OSDiskUri -CreateOption fromImage -ErrorAction Stop

        if ($Disks)
        {
            Write-Verbose "Adding $($Disks.Count) data disks"
            $lun = 0
        
            foreach ($Disk in $Disks.GetEnumerator())
            {
                $DataDiskName = $Disk.Key.ToLower()
                $DiskSize = $Disk.Value
                $VhdUri = "$($StorageContext.BlobEndpoint)automatedlabdisks/$DataDiskName.vhd"

                Write-Verbose -Message "Calling 'Add-AzureRmVMDataDisk' for $DataDiskName with $DiskSize GB on LUN $lun (resulting in uri $VhdUri)"
                $vm = $vm | Add-AzureRmVMDataDisk -Name $DataDiskName -VhdUri $VhdUri -Caching None -DiskSizeInGB $DiskSize -Lun $lun -CreateOption Empty                
                $lun++
            }
        }
           
        Write-ProgressIndicator        

        #Add any additional NICs to the VM configuration
        if ($Machine.NetworkAdapters.Count -gt 1)
        {
            Write-Verbose -Message "Adding $($Machine.NetworkAdapters.Count) additional NICs to the VM config"
            foreach ($adapter in ($Machine.NetworkAdapters | Where-Object Ipv4Address -ne $defaultIPv4Address))
            {
                if ($adapter.Ipv4Address.ToString() -ne $defaultIPv4Address)
                {
                    $adapterStartAddress = Get-NetworkRange -IPAddress ($adapter.Ipv4Address.AddressAsString) -SubnetMask ($adapter.Ipv4Address.Ipv4Prefix) | Select-Object -First 1
                    $additionalSubnet = (Get-AzureRmVirtualNetwork -ResourceGroupName $ResourceGroupName | Where-Object { $_.AddressSpace.AddressPrefixes.Contains($adapterStartAddress) })[0] |
                    Get-AzureRmVirtualNetworkSubnetConfig
        
                    Write-Verbose -Message "adapterStartAddress = '$adapterStartAddress'"
                    $vNet = $LabVirtualNetworkDefinition | Where-Object { $_.AddressSpace.AddressAsString -eq $adapterStartAddress }
                    if ($vNet)
                    {
                        Write-Verbose -Message "Adding additional network adapter with Vnet '$($vNet.Name)' in subnet '$adapterStartAddress' with IP address '$($adapter.Ipv4Address.AddressAsString)'"
                        $networkInterface = New-AzureRmNetworkInterface -Name ($adapter.Ipv4Address.AddressAsString) `
                        -ResourceGroupName $ResourceGroupName -Location $Location `
                        -Subnet $additionalSubnet -PrivateIpAddress ($adapter.Ipv4Address.AddressAsString)
        
                        $vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $networkInterface.Id -ErrorAction Stop
                    }
                    else
                    {
                        throw "Vnet could not be determined for network adapter with IP address of '$(Get-NetworkRange -IPAddress ($adapter.Ipv4Address.AddressAsString) -SubnetMask ($adapter.Ipv4Address.Ipv4Prefix)))'"
                    }
                }
            }
        }

        Write-Verbose -Message 'Calling New-AzureRMVm'
        New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $Location -VM $vm -Tags @{ AutomatedLab = $script:lab.Name; CreationTime = Get-Date } -ErrorAction Stop
    }

    Write-LogFunctionExit
}
#endregion New-LWAzureVM

#region Initialize-LWAzureVM
function Initialize-LWAzureVM
{
    [Cmdletbinding()]
    Param (
        [Parameter(Mandatory)]
        [AutomatedLab.Machine[]]$Machine
    )

    $initScript = {
        param(
            [Parameter(Mandatory = $true)]
            $MachineSettings
        )

        #region Region Settings Xml
        $regionSettings = @'
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
 
 <!-- user list -->
 <gs:UserList>
    <gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
 </gs:UserList>
 
 <!-- GeoID -->
 <gs:LocationPreferences>
    <gs:GeoID Value="{1}"/>
 </gs:LocationPreferences>
 
 <!-- system locale -->
 <gs:SystemLocale Name="{0}"/>
 
<!-- user locale -->
 <gs:UserLocale>
    <gs:Locale Name="{0}" SetAsCurrent="true" ResetAllSettings="true"/>
 </gs:UserLocale>
 
</gs:GlobalizationServices>
'@

        #endregion

        $geoId = 94 #default is US

        $computerName = ($env:ComputerName).ToUpper()
        $tempFile = [System.IO.Path]::GetTempFileName()
        $regsettings = ($MachineSettings."$computerName")[1]
        Write-Verbose -Message "Regional Settings for $computerName`: $regsettings"
        $regionSettings -f ($MachineSettings."$computerName")[0], $geoId | Out-File -FilePath $tempFile
        $argument = 'intl.cpl,,/f:"{0}"' -f $tempFile
        control.exe $argument
        Start-Sleep -Seconds 1
        Remove-Item -Path $tempFile

        Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine -Force

        #Set Power Scheme to High Performance
        powercfg.exe -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

        #Map the Azure lab source drive
        $azureCredential = New-Object pscredential (($MachineSettings."$computerName")[4], (ConvertTo-SecureString -String ($MachineSettings."$computerName")[5] -AsPlainText -Force))
        
        $azureDrive = New-PSDrive -Name X -PSProvider FileSystem -Root ($MachineSettings."$computerName")[3] -Description 'Azure lab sources' -Persist -Credential $azureCredential -ErrorAction SilentlyContinue
        if(-not $azureDrive)
        {
            Write-Warning "Could not map $(($MachineSettings."$computerName")[3]) as drive X. Post-installations might fail."
        }
        
        #set the time zone
        $timezone = ($MachineSettings."$computerName")[1]
        Write-Verbose -Message "Time zone for $computerName`: $regsettings"
        tzutil.exe /s $regsettings

        reg.exe add 'HKLM\SOFTWARE\Microsoft\ServerManager\oobe' /v DoNotOpenInitialConfigurationTasksAtLogon /d 1 /t REG_DWORD /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\ServerManager' /v DoNotOpenServerManagerAtLogon /d 1 /t REG_DWORD /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' /v EnableFirstLogonAnimation /d 0 /t REG_DWORD /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v FilterAdministratorToken /t REG_DWORD /d 0 /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v EnableLUA /t REG_DWORD /d 0 /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}' /v IsInstalled /t REG_DWORD /d 0 /f #disable admin IE Enhanced Security Configuration
        reg.exe add 'HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}' /v IsInstalled /t REG_DWORD /d 0 /f #disable user IE Enhanced Security Configuration

        #turn off the Windows firewall
        #netsh.exe advfirewall set domain state off
        #netsh.exe advfirewall set private state off
        #netsh.exe advfirewall set public state off
        
        $disks = ($MachineSettings."$computerName")[2]
        Write-Verbose -Message "Disk count for $computerName`: $disks"
        if ([int]$disks -gt 0)
        {
            $diskpartCmd = 'LIST DISK'

            $disks = $diskpartCmd | diskpart.exe

            foreach ($line in $disks)
            {
                if ($line -match 'Disk (?<DiskNumber>\d) \s+(Online|Offline)\s+(?<Size>\d+) GB\s+(?<Free>\d+) GB')
                {
                    $nextDriveLetter = [char[]](67..90) | 
                    Where-Object { (Get-WmiObject -Class Win32_LogicalDisk | 
                    Select-Object -ExpandProperty DeviceID) -notcontains "$($_):"} | 
                    Select-Object -First 1

                    $diskNumber = $Matches.DiskNumber

                    $diskpartCmd = "@
                        SELECT DISK $diskNumber
                        ATTRIBUTES DISK CLEAR READONLY
                        ONLINE DISK
                        CREATE PARTITION PRIMARY
                        ASSIGN LETTER=$nextDriveLetter
                        EXIT
                    @"

                    $diskpartCmd | diskpart.exe | Out-Null

                    Start-Sleep -Seconds 2

                    cmd.exe /c "echo y | format $($nextDriveLetter): /q /v:DataDisk$diskNumber"
                }
        
            }
        }
    }

    Write-LogFunctionEntry
    
    $lab = Get-Lab

    Write-ScreenInfo -Message 'Waiting for all machines to be visible in Azure'
    while ((Get-AzureRmVM -ResourceGroupName $lab.Name -WarningAction SilentlyContinue | Where-Object Name -in $Machine.Name).Count -ne $Machine.Count)
    {        
        Start-Sleep -Seconds 10
        Write-Verbose 'Still waiting for all machines to be visible in Azure'
    }
    Write-ScreenInfo -Message "$($Machine.Count) new machine(s) has been created and now visible in Azure"
    Write-ScreenInfo -Message 'Waiting until all machines have a DNS name in Azure'
    while ((Get-LabMachine).AzureConnectionInfo.DnsName.Count -ne (Get-LabMachine).Count)
    {
        Start-Sleep -Seconds 10
        Write-ScreenInfo -Message 'Still waiting until all machines have a DNS name in Azure'
    }
    Write-ScreenInfo -Message "DNS names found: $((Get-LabMachine).AzureConnectionInfo.DnsName.Count)"

    #refresh the machine list to have also Azure meta date is available
    $Machine = Get-LabMachine -ComputerName $Machine
    
    #Point out first added machine as staging machine for staging Tools folder and alike
    $stagingMachine = $Machine[0]
    
    #copy AL tools to lab machine and optionally the tools folder
    Write-ScreenInfo -Message "Waiting for machine '$stagingMachine' to be accessible" -NoNewLine
    Wait-LabVM -ComputerName $stagingMachine -ProgressIndicator 15 -ErrorAction Stop
    
    $toolsDestination = "$($stagingMachine.ToolsPath)"
    if ($stagingMachine.ToolsPathDestination)
    {
        $toolsDestination = "$($stagingMachine.ToolsPathDestination)"
    }
    
    if ($Machine | Where-Object {$_.ToolsPath -ne ''})
    {
        #Compress all tools for all machines into one zip file
        $tempFolderPath = [System.IO.Path]::GetTempFileName()
        Remove-Item -Path $tempFolderPath
        $tempFolderPath = "$tempFolderPath.tmp"
        
        New-Item -ItemType Directory -Path "$tempFolderPath.tmp" | Out-Null
        
        $tempFilePath = [System.IO.Path]::GetTempFileName()
        Remove-Item -Path $tempFilePath
        $tempFilePath = $tempFilePath -replace '\.tmp', '.zip'
        
        foreach ($m in $Machine)
        {
            New-Item -ItemType Directory -Path "$tempFolderPath\$($m.Name)" | Out-Null
            if ($m -ne $stagingMachine -and $m.ToolsPath -and $m.ToolsPath -eq $stagingMachine.ToolsPath)
            {
                New-Item -ItemType File -Path "$tempFolderPath\$($m.Name)\Replica-$($stagingMachine.Name)" | Out-Null
            }
            elseif ($m.ToolsPath)
            {
                Get-ChildItem -Path "$($m.ToolsPath)" | Copy-Item -Destination "$tempFolderPath\$($m.Name)" -Recurse
            }
        }   
        
        Write-Verbose -Message "Tools destination for staging machine: $($toolsDestination)"
        
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::CreateFromDirectory($tempFolderPath, $tempFilePath) 
        
        
        Write-ScreenInfo -Message "Starting copy of Tools ($([int]((Get-Item $tempfilepath).length/1kb)) KB) to staging machine '$stagingMachine'" -TaskStart
        Send-File -Source $tempFilePath -Destination C:\AutomatedLabTools.zip -Session (New-LabPSSession -ComputerName $stagingMachine)
        Write-ScreenInfo -Message 'Finished' -TaskEnd
        
        Remove-Item -Path $tempFilePath -Force
        Remove-Item -Path $tempFolderPath -Recurse -Force
        
        
        #Expand files on staging machine and create a share for other machines to access
        $job = Invoke-LabCommand -ComputerName $stagingMachine -ActivityName 'Expanding Tools Zip File' -NoDisplay -ArgumentList $toolsDestination -ScriptBlock `
        {
            param
            (
                [string]$ToolsDestination
            )
        
            if (-not (Test-Path 'C:\AutomatedLabTools'))
            {
                New-Item -ItemType Directory -Path 'C:\AutomatedLabTools' | Out-Null
            }

            if (-not (Test-Path $ToolsDestination))
            {
                New-Item -ItemType Directory -Path $ToolsDestination | Out-Null
            }
            
            $shell = New-Object -ComObject Shell.Application
            $shell.namespace('C:\AutomatedLabTools').CopyHere($shell.Namespace('C:\AutomatedLabTools.zip').Items()) 
            
            if (Test-Path "C:\AutomatedLabTools\$(Hostname.exe)")
            {
                Get-ChildItem -Path "C:\AutomatedLabTools\$(Hostname.exe)" | Copy-Item -Destination $ToolsDestination -Recurse
            }

            $shareClass = [WMICLASS]'WIN32_Share'
            $shareClass.Create('C:\AutomatedLabTools', 'AutomatedLabTools', 0)
        } -AsJob -PassThru
    
        Write-ScreenInfo -Message 'Waiting for Tools to be extracted on staging machine' -NoNewLine
        Wait-LWLabJob -Job $job -ProgressIndicator 5 -Timeout 30 -NoDisplay
    
    
        Write-ScreenInfo -Message 'Waiting for all machines to be accessible' -TaskStart -NoNewLine
        Write-Verbose "Staging machine is '$stagingMachine'"
        $otherMachines = Get-LabMachine | Where-Object Name -ne $stagingMachine
        #if the lab has not just one machine, wait for other machines
        if ($otherMachines)
        {
            Write-Verbose "Other machines are '$($otherMachines -join '. ')'"
            Wait-LabVM -ComputerName $otherMachines -ProgressIndicator 15 -ErrorAction Stop
        }
        Write-ScreenInfo -Message 'All machines are now accessible' -TaskEnd
        
        Write-ScreenInfo -Message 'Starting copy of Tools content to all machines' -TaskStart
        
        if ($otherMachines)
        {
            $jobs = Invoke-LabCommand -ComputerName $otherMachines -NoDisplay -AsJob -PassThru -ActivityName 'Copy tools from staged folder' -ScriptBlock `
            {
                param
                (
                    [Parameter(Mandatory = $true)]
                    [string]$Server,
                    
                    [Parameter(Mandatory = $true)]
                    [string]$User,
                    
                    [Parameter(Mandatory = $true)]
                    [string]$Password,
                    
                    [string]$ToolsDestination
                )
                
                #Remove-Item -Path C:\Tools -Recurse
                $backupErrorActionPreference = $ErrorActionPreference
                $ErrorActionPreference = 'SilentlyContinue'
                
                net.exe use * "\\$Server\AutomatedLabTools" /user:$Server\$User $Password | Out-Null
                $ErrorActionPreference = $backupErrorActionPreference
            
                write-host '3'
                if (Test-Path "\\$Server\AutomatedLabTools\$(Hostname.exe)\Replica-*")
                {
                    $source = (Get-Item "\\$Server\AutomatedLabTools\$(Hostname.exe)\Replica-*").Name.Split('-', 2)[1]
                    Copy-Item "\\$Server\AutomatedLabTools\$source" -Destination $ToolsDestination -Recurse
                }
                else
                {
                    Copy-Item "\\$Server\AutomatedLabTools\$(Hostname.exe)" -Destination $ToolsDestination -Recurse
                }
                $backupErrorActionPreference = $ErrorActionPreference
                $ErrorActionPreference = 'SilentlyContinue'
                
                net.exe use "\\$Server\AutomatedLabTools" /delete /yes | Out-Null
                $ErrorActionPreference = $backupErrorActionPreference
                
            } -ArgumentList $stagingMachine.NetworkAdapters[0].Ipv4Address.IpAddress, $stagingMachine.InstallationUser.UserName, $stagingMachine.InstallationUser.Password, $toolsDestination
        }
    }
    Write-ScreenInfo -Message 'Finished' -TaskEnd

    Write-ScreenInfo -Message 'Configuring localization and additional disks' -TaskStart -NoNewLine
    $machineSettings = @{}
    foreach ($m in $Machine)
    {
        $machineSettings.Add($m.Name.ToUpper(), @($m.UserLocale, $m.TimeZone, [int]($m.Disks.Count), (Get-LabAzureLabSourcesStorage).Path, (Get-LabAzureLabSourcesStorage).StorageAccountName, (Get-LabAzureLabSourcesStorage).StorageAccountKey))
    }
    $jobs = Invoke-LabCommand -ComputerName $Machine -ActivityName VmInit -ScriptBlock $initScript -UseLocalCredential -ArgumentList $machineSettings -NoDisplay -AsJob -PassThru
    Wait-LWLabJob -Job $jobs -ProgressIndicator 5 -Timeout 30 -NoDisplay
    Write-ScreenInfo -Message 'Finished' -TaskEnd
    
    Enable-LabVMRemoting -ComputerName $Machine
    
    Write-ScreenInfo -Message 'Stopping all new machines except domain controllers'
    $machinesToStop = $Machine | Where-Object { $_.Roles.Name -notcontains 'RootDC' -and $_.Roles.Name -notcontains 'FirstChildDC' -and $_.Roles.Name -notcontains 'DC' -and $_.IsDomainJoined }
    if ($machinesToStop)
    {
        Stop-LWAzureVM -ComputerName $machinesToStop
        Wait-LabVMShutdown -ComputerName $machinesToStop
    }
    
    if ($machinesToStop)
    {
        Write-ScreenInfo -Message "$($Machine.Count) new Azure machines was configured. Some machines were stopped as they are not to be domain controllers '$($machinesToStop -join ', ')'"
    }
    else
    {
        Write-ScreenInfo -Message "($($Machine.Count)) new Azure machines was configured"
    }
        
    Write-LogFunctionExit
}
#endregion Initialize-LWAzureVM


#region Remove-LWAzureVM
function Remove-LWAzureVM
{
    Param (
        [Parameter(Mandatory)]
        [string]$ComputerName,
        
        [switch]$AsJob,
        
        [switch]$PassThru
    )
    
    Write-LogFunctionEntry

    $Lab = Get-Lab
    
    if ($AsJob)
    {
        $job = Start-Job -ScriptBlock {
            param (
                [Parameter(Mandatory)]
                [hashtable]$ComputerName,
                [Parameter(Mandatory)]
                [string]$SubscriptionPath
            )
            
            Import-Module -Name Azure*
            Select-AzureRmProfile -Path $SubscriptionPath

            $resourceGroup = ((Get-LabMachine -ComputerName $ComputerName).AzureConnectionInfo.ResourceGroupName)

            $vm = Get-AzureRmVM -ResourceGroupName $resourceGroup -Name $ComputerName -WarningAction SilentlyContinue
            
            $vm | Remove-AzureRmVM -Force
        } -ArgumentList $ComputerName,$Lab.AzureSettings.AzureProfilePath
        
        if ($PassThru)
        {
            $job
        }
    }
    else
    {
        $resourceGroup = ((Get-LabMachine -ComputerName $ComputerName).AzureConnectionInfo.ResourceGroupName)
        $vm = Get-AzureRmVM -ResourceGroupName $resourceGroup -Name $ComputerName -WarningAction SilentlyContinue
        
        $result = $vm | Remove-AzureRmVM -Force
    }
    
    Write-LogFunctionExit
}
#endregion Remove-LWAzureVM

#region Start-LWAzureVM
function Start-LWAzureVM
{
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$ComputerName,

        [int]$DelayBetweenComputers = 0,

        [int]$ProgressIndicator = 15,

        [switch]$NoNewLine
    )
    
    Write-LogFunctionEntry
    
    # This is ugly and will likely change in one of the next AzureRM module updates. PowerState is indeed a string literal instead of an Enum
    $azureVms = Get-AzureRmVM -WarningAction SilentlyContinue -Status -ResourceGroupName (Get-LabAzureDefaultResourceGroup).ResourceGroupName
    if (-not $azureVms)
    {
        throw 'Get-AzureRmVM did not return anything, stopping lab deployment. Code will be added to handle this error soon'
    }
    $resourceGroups = (Get-LabMachine -ComputerName $ComputerName).AzureConnectionInfo.ResourceGroupName | Select-Object -Unique
    $azureVms = $azureVms | Where-Object { $_.PowerState -ne 'VM running' -and  $_.Name -in $ComputerName -and $_.ResourceGroupName -in $resourceGroups }

    $retries = 5
    $machinesToJoin = @()
    
    foreach ($name in $ComputerName)
    {
        $vm = $azureVms | Where-Object Name -eq $name

        do {
            $result = $vm | Start-AzureRmVM -ErrorAction SilentlyContinue
            if ($result.Status -ne 'Succeeded')
            {
                Start-Sleep -Seconds 10
            }
            $retries--
        }
        until ($retries -eq 0 -or $result.Status -eq 'Succeeded')
        
        if ($result.Status -ne 'Succeeded')
        {
            throw "Could not start machine '$name'"
        }
        else
        {
            $machine = Get-LabMachine -ComputerName $name
            #if the machine should be domain-joined but has not yet joined and is not a domain controller
            if ($machine.IsDomainJoined -and -not $machine.HasDomainJoined -and ($machine.Roles.Name -notcontains 'RootDC' -and $machine.Roles.Name -notcontains 'FirstChildDC' -and $machine.Roles.Name -notcontains 'DC'))
            {
                $machinesToJoin += $machine
            }
        }

        Start-Sleep -Seconds $DelayBetweenComputers
    }

    if ($machinesToJoin)
    {
        Write-Verbose -Message "Waiting for machines '$($machinesToJoin -join ', ')' to come online"
        Wait-LabVM -ComputerName $machinesToJoin -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine

        Write-Verbose -Message 'Start joining the machines to the respective domains'
        Join-LabVMDomain -Machine $machinesToJoin
    }
    
    Write-LogFunctionExit
}
#endregion Start-LWAzureVM

#region Stop-LWAzureVM
function Stop-LWAzureVM
{
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName,

        [int]$ProgressIndicator,

        [switch]$NoNewLine,

        [switch]$ShutdownFromOperatingSystem
    )
    
    Write-LogFunctionEntry
    
    $lab = Get-Lab
    $azureVms = Get-AzureRmVM -WarningAction SilentlyContinue 
    $resourceGroups = (Get-LabMachine -ComputerName $ComputerName).AzureConnectionInfo.ResourceGroupName | Select-Object -Unique
    $azureVms = $azureVms | Where-Object { $_.Name -in $ComputerName -and $_.ResourceGroupName -in $resourceGroups }
    
    if ($ShutdownFromOperatingSystem)
    {
        $jobs = @()
        $jobs = Invoke-LabCommand -ComputerName $ComputerName -NoDisplay -AsJob -PassThru -ScriptBlock { shutdown.exe -s -t 0 -f }
        Wait-LWLabJob -Job $jobs -NoDisplay -ProgressIndicator $ProgressIndicator
        $failedJobs = $jobs | Where-Object {$_.State -eq 'Failed'}
        if ($failedJobs)
        {
            Write-ScreenInfo -Message "Could not stop Azure VM(s): '$($failedJobs.Location)'" -Type Error
        }
    }
    else
    {
        $jobs = @()        

        foreach ($name in $ComputerName)
        {
            $vm = $azureVms | Where-Object Name -eq $name            
            $jobs += Start-Job -Name "StopAzureVm_$name" -ScriptBlock {
            param
            (
                [object]$Machine,
                [string]$SubscriptionPath
            )
                Import-Module -Name Azure*
                Select-AzureRmProfile -Path $SubscriptionPath
                $result = $Machine | Stop-AzureRmVM -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Force

                if ($result.Status -ne 'Succeeded')
                {
                    Write-Error -Message 'Could not stop Azure VM' -TargetObject $Machine.Name
                }
            } -ArgumentList @($vm, $lab.AzureSettings.AzureProfilePath)
        }

        Wait-LWLabJob -Job $jobs -NoDisplay -ProgressIndicator $ProgressIndicator
        $failedJobs = $jobs | Where-Object {$_.State -eq 'Failed'}
        if ($failedJobs)
        {
            $jobNames = ($failedJobs | foreach {if($_.Name.StartsWith("StopAzureVm_")){($_.Name -split "_")[1]}}) -join ", "
            Write-ScreenInfo -Message "Could not stop Azure VM(s): '$jobNames'" -Type Error
        }

    }
    
    if ($ProgressIndicator -and (-not $NoNewLine))
    {
        Write-ProgressIndicatorEnd
    }
    
    Write-LogFunctionExit
}

#endregion Stop-LWAzureVM

#region Wait-LWAzureRestartVM
function Wait-LWAzureRestartVM
{
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName,
        
        [double]$TimeoutInMinutes = 15,

        [int]$ProgressIndicator,

        [switch]$NoNewLine
    )

    #required to suporess verbose messages, warnings and errors
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    
    Write-LogFunctionEntry
    
    $start = (Get-Date).ToUniversalTime()
    
    Write-Verbose -Message "Starting monitoring the servers at '$start'"
    
    $machines = Get-LabMachine -ComputerName $ComputerName
    
    $cmd = {
        param (
            [datetime]$Start
        )
        
        $Start = $Start.ToLocalTime()

        $events = Get-EventLog -LogName System -InstanceId 2147489653 -After $Start -Before $Start.AddMinutes(40)
        
        $events
    }
    
    $ProgressIndicatorTimer = (Get-Date)

    do
    {
        $machines = foreach ($machine in $machines)
        {
            if (((Get-Date) - $ProgressIndicatorTimer).TotalSeconds -ge $ProgressIndicator)
            {
                Write-ProgressIndicator
                $ProgressIndicatorTimer = (Get-Date)
            }
            
            $events = Invoke-LabCommand -ComputerName $machine -ActivityName WaitForRestartEvent -ScriptBlock $cmd -ArgumentList $start.Ticks -UseLocalCredential -PassThru -Verbose:$false -NoDisplay -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

            if (-not $events)
            {
                $events = Invoke-LabCommand -ComputerName $machine -ActivityName WaitForRestartEvent -ScriptBlock $cmd -ArgumentList $start.Ticks -PassThru -Verbose:$false -NoDisplay -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
            }
            
            if ($events)
            {
                Write-Verbose -Message "VM '$machine' has been restarted"
            }
            else
            {
                $machine
            }
        }
    }
    until ($machines.Count -eq 0 -or (Get-Date).ToUniversalTime().AddMinutes(-$TimeoutInMinutes) -gt $start)
    
    if (-not $NoNewLine)
    {
        Write-ProgressIndicatorEnd
    }
    
    if ((Get-Date).ToUniversalTime().AddMinutes(-$TimeoutInMinutes) -gt $start)
    {
        foreach ($machine in ($machines))
        {
            Write-Error -Message "Timeout while waiting for computers to restart. Computers '$machine' not restarted" -TargetObject $machine
        }
    }
    
    Write-Verbose -Message "Finished monitoring the servers at '$(Get-Date)'"
    
    Write-LogFunctionExit
}
#endregion Wait-LWAzureRestartVM

#region Get-LWAzureVMStatus
function Get-LWAzureVMStatus
{
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    #required to suporess verbose messages, warnings and errors
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    
    Write-LogFunctionEntry
    
    $result = @{ }
    $azureVms = Get-AzureRmVM -WarningAction SilentlyContinue -Status (Get-LabAzureDefaultResourceGroup).ResourceGroupName
    if (-not $azureVms)
    {
        throw 'Get-AzureRmVM did not return anything, stopping lab deployment. Code will be added to handle this error soon'
    }
    $resourceGroups = (Get-LabMachine).AzureConnectionInfo.ResourceGroupName | Select-Object -Unique
    $azureVms = $azureVms | Where-Object { $_.Name -in $ComputerName -and $_.ResourceGroupName -in $resourceGroups }
    
    foreach ($azureVm in $azureVms)
    {
        if ($azureVm.PowerState -eq 'VM running')
        {
            $result.Add($azureVm.Name, 'Started')
        }
        elseif ($azureVm.PowerState -eq 'VM stopped' -or $azureVm.PowerState -eq 'VM deallocated')
        {
            $result.Add($azureVm.Name, 'Stopped')
        }
        else
        {
            $result.Add($azureVm.Name, 'Unknown')
        }
    }
    
    $result
    
    Write-LogFunctionExit
}
#endregion Get-LWAzureVMStatus

#region Get-LWAzureVMConnectionInfo
function Get-LWAzureVMConnectionInfo
{
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )
    
    Write-LogFunctionEntry

    $azureVMs = Get-AzureRmVM -WarningAction SilentlyContinue | Where-Object ResourceGroupName -in (Get-LabAzureResourceGroup).ResourceGroupName | Where-Object Name -in $ComputerName
    
    foreach ($name in $ComputerName)
    {
        $azureVM = $azureVMs | Where-Object Name -eq $name

        if (-not $azureVM)
        { return }        

        $nic = Get-AzureRmNetworkInterface | Where {$_.virtualmachine.id -eq ($azureVM.Id)}
        $ip = Get-AzureRmPublicIpAddress | where {$_.Id -eq $nic.IpConfigurations.publicipaddress.id}

        # Why are DnsName and HttpsName being used? Seems like it would be the same anyway...
        New-Object PSObject -Property @{
            ComputerName = $name
            DnsName = $ip.DnsSettings.Fqdn
            HttpsName = $ip.DnsSettings.Fqdn
            VIP = $ip.IpAddress
            Port = 5985
            RdpPort = 3389
            ResourceGroupName = $azureVM.ResourceGroupName
        }
    }
    
    Write-LogFunctionExit
}
#endregion Get-LWAzureVMConnectionInfo

#region Enable-LWAzureVMRemoting
function Enable-LWAzureVMRemoting
{
    param(
        [Parameter(Mandatory, Position = 0)]
        [string[]]$ComputerName,
        [switch]$UseSSL
    )

    if ($ComputerName)
    {
        $machines = Get-LabMachine -All | Where-Object Name -in $ComputerName
    }
    else
    {
        $machines = Get-LabMachine -All
    }
    
    $script = {
        param ($DomainName, $UserName, $Password)
        
        $RegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
        
        Set-ItemProperty -Path $RegPath -Name AutoAdminLogon -Value 1 -ErrorAction SilentlyContinue
        Set-ItemProperty -Path $RegPath -Name DefaultUserName -Value $UserName -ErrorAction SilentlyContinue
        Set-ItemProperty -Path $RegPath -Name DefaultPassword -Value $Password -ErrorAction SilentlyContinue
        Set-ItemProperty -Path $RegPath -Name DefaultDomainName -Value $DomainName -ErrorAction SilentlyContinue
        
        #Enable-WSManCredSSP works fine when called remotely on 2012 servers but not on 2008 (Access Denied). In case Enable-WSManCredSSP fails
        #the settings are done in the registry directly
        try
        {
            Enable-WSManCredSSP -Role Server -Force | Out-Null
        }
        catch
        {
            New-ItemProperty -Path HKLM:\software\Microsoft\Windows\CurrentVersion\WSMAN\Service -Name auth_credssp -Value 1 -PropertyType DWORD -Force
            New-ItemProperty -Path HKLM:\software\Microsoft\Windows\CurrentVersion\WSMAN\Service -Name allow_remote_requests -Value 1 -PropertyType DWORD -Force
        }
    }
    
    foreach ($machine in $machines)
    {
        $cred = $machine.GetCredential((Get-Lab))
        try
        {
            Invoke-LabCommand -ComputerName $machine -ActivityName SetLabVMRemoting -NoDisplay -ScriptBlock $script `
            -ArgumentList $machine.DomainName, $cred.UserName, $cred.GetNetworkCredential().Password -ErrorAction Stop
        }
        catch
        {
            if ($UseSSL)
            {
                Connect-WSMan -ComputerName $machine.AzureConnectionInfo.DnsName -Credential $cred -Port $machine.AzureConnectionInfo.Port -UseSSL -SessionOption (New-WSManSessionOption -SkipCACheck -SkipCNCheck)
            }
            else
            {
                Connect-WSMan -ComputerName $machine.AzureConnectionInfo.DnsName -Credential $cred -Port $machine.AzureConnectionInfo.Port
            }
            Set-Item -Path "WSMan:\$($machine.AzureConnectionInfo.DnsName)\Service\Auth\CredSSP" -Value $true
            Disconnect-WSMan -ComputerName $machine.AzureConnectionInfo.DnsName
        }
    }
}
#endregion Enable-LWAzureVMRemoting