AutomatedLabVMWareWorkerVirtualMachines.psm1

<#
        An AutomatedLab user could have a VMWare environment on premisis _and_ have Hyper-V enabled on his own device.
        When the user has both Hyper-V and VMWare modules loaded in one session, this can cause unwanted behaviour.

        This may need to be mitigated in AutomatedLab in some cases. There are two main approaches:
        1) Prepending conflicting CmdLets with the name of the intende module of origin, e.g.:
        VMware.VimAutomatiion.Core\Get-VM

        2) Theoretically, one should be able to load modules using the -Prefix parameter:
        Import-Module VMWare* -Prefix "VW"
        This _should_ result in all the VMWare CmdLets being imported with the prefix VW:
        Get-VWVM
        Unfortunately, this does not work in PowerCLI 6.0R2 - 6.5.0 , because of the way the underlying PSSnapin is loaded.
        See here for more information:
        https://communities.vmware.com/thread/520601
        This will be solved in PowerCLI version 6.5.1 R1:
        https://blogs.vmware.com/PowerCLI/2016/11/saying-farewell-snapins.html

        For now, approach 1) is probably the way to go, or we should force users to require version 6.5.1R1 or greater for the VMware module.

        Get a list of CmdLets whith the same name in Hyper-V and VMWare:
        Compare-Object (get-command -Module vmware.vimautomation.core) (Get-Command -Module Hyper-v) -IncludeEqual -ExcludeDifferent
        Retrieved 30-5-2017, Vmware module version:6.3.0.0, Hyper-V module version 2.0.0.0

        InputObject SideIndicator
        ----------- -------------
        Get-VM ==
        Get-VMHost ==
        Move-VM ==
        New-VM ==
        Remove-VM ==
        Restart-VM ==
        Set-VM ==
        Set-VMHost ==
        Start-VM ==
        Stop-VM ==
        Suspend-VM ==
#>


trap
{
    if ((($_.Exception.Message -like '*Get-VM*') -or `
            ($_.Exception.Message -like '*Save-VM*') -or `
            ($_.Exception.Message -like '*Get-VMSnapshot*') -or `
            ($_.Exception.Message -like '*Suspend-VM*') -or `
        ($_.Exception.Message -like '*CheckPoint-VM*')) -and `
    (-not (Get-Module -ListAvailable Hyper-V)))
    {
        # What is the exact purpose of this error trap?
        # Errors concerning certain CmdLets are to be ignored, if Hyper-V is not an available module. Why?
    }
    else
    {
        Write-Error $_
    }
    continue
}

#region New-LWVMWareVM
function New-LWVMWareVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    [Cmdletbinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$Name,

        [Parameter(Mandatory)]
        [string]$ReferenceVM,

        [Parameter(Mandatory)]
        [string]$AdminUserName,

        [Parameter(Mandatory)]
        [string]$AdminPassword,

        [Parameter(ParameterSetName = 'DomainJoin')]
        [string]$DomainName,

        [Parameter(Mandatory, ParameterSetName = 'DomainJoin')]
        [pscredential]$DomainJoinCredential,

        [switch]$AsJob,

        [switch]$PassThru
    )

    Write-LogFunctionEntry

    $lab = Get-Lab

    #TODO: add logic to determine if machine already exists
    <#
            if (Get-VM -Name $Machine.Name -ErrorAction SilentlyContinue)
            {
            Write-ProgressIndicatorEnd
            Write-ScreenInfo -Message "The machine '$Machine' does already exist" -Type Warning
            return $false
            }

            Write-Verbose "Creating machine with the name '$($Machine.Name)' in the path '$VmPath'"

    #>


    $folderName = "AutomatedLab_$($lab.Name)"
    if (-not (Get-Folder -Name $folderName -ErrorAction SilentlyContinue))
    {
        New-Folder -Name $folderName -Location VM | out-null
    }


    $referenceSnapshot = (Get-Snapshot -VM (VMware.VimAutomation.Core\Get-VM $ReferenceVM)).Name | Select-Object -last 1

    $parameters = @{
        Name = $Name
        ReferenceVM = $ReferenceVM
        AdminUserName = $AdminUserName
        AdminPassword = $AdminPassword
        DomainName = $DomainName
        DomainCred = $DomainJoinCredential
        FolderName = $FolderName
    }

    if ($AsJob)
    {
        $job = Start-Job -ScriptBlock {
            throw 'Not implemented yet'  # TODO: implement
        } -ArgumentList $parameters


        if ($PassThru)
        {
            $job
        }
    }
    else
    {
        $osSpecs = Get-OSCustomizationSpec -Name AutomatedLabSpec -Type NonPersistent -ErrorAction SilentlyContinue
        if ($osSpecs)
        {
            Remove-OSCustomizationSpec -OSCustomizationSpec $osSpecs -Confirm:$false
        }

        if (-not $parameters.DomainName)
        {
            $osSpecs = New-OSCustomizationSpec -Name AutomatedLabSpec -FullName $parameters.AdminUserName -AdminPassword $parameters.AdminPassword `
            -OSType Windows -Type NonPersistent -OrgName AutomatedLab -Workgroup AutomatedLab -ChangeSid
            #$osSpecs = Get-OSCustomizationSpec -Name Standard | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $ipaddress -SubnetMask $netmask -DefaultGateway $gateway -Dns $DNS
        }
        else
        {
            $osSpecs = New-OSCustomizationSpec -Name AutomatedLabSpec -FullName $parameters.AdminUserName -AdminPassword $parameters.AdminPassword `
            -OSType Windows -Type NonPersistent -OrgName AutomatedLab -Domain $parameters.DomainName -DomainCredentials $DomainJoinCredential -ChangeSid
        }

        $ReferenceVM_int = VMware.VimAutomation.Core\Get-VM -Name $parameters.ReferenceVM
        if (-not $ReferenceVM_int)
        {
            Write-Error "Reference VM '$($parameters.ReferenceVM)' could not be found, cannot create the machine '$($machine.Name)'"
            return
        }

        # Create Linked Clone
        $result = VMware.VimAutomation.Core\New-VM `
        -Name $parameters.Name `
        -ResourcePool $lab.VMWareSettings.ResourcePool `
        -Datastore $lab.VMWareSettings.DataStore `
        -Location (Get-Folder -Name $parameters.FolderName) `
        -OSCustomizationSpec $osSpecs `
        -VM $ReferenceVM_int `
        -LinkedClone `
        -ReferenceSnapshot $referenceSnapshot `

        #TODO: logic to switch to full clone for AD recovery scenario's etc.
        <# Create full clone
                $result = New-VM `
                -Name $parameters.Name `
                -ResourcePool $lab.VMWareSettings.ResourcePool `
                -Datastore $lab.VMWareSettings.DataStore `
                -Location (Get-Folder -Name $parameters.FolderName) `
                -OSCustomizationSpec $osSpecs `
                -VM $ReferenceVM_int
        #>

    }

    if ($PassThru)
    {
        $result
    }

    Write-LogFunctionExit
}
#endregion New-LWVM

#region Remove-LWVMWareVM
function Remove-LWVMWareVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    Param (
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [switch]$AsJob,

        [switch]$PassThru
    )

    Write-LogFunctionEntry

    if ($AsJob)
    {
        $job = Start-Job -ScriptBlock {
            param (
                [Parameter(Mandatory)]
                [hashtable]$ComputerName
            )

            Add-PSSnapin -Name VMware.VimAutomation.Core, VMware.VimAutomation.Vds

            $vm = VMware.VimAutomation.Core\Get-VM -Name $ComputerName
            if ($vm)
            {
                if ($vm.PowerState -eq "PoweredOn")
                {
                    VMware.VimAutomation.Core\Stop-vm -VM $vm -Confirm:$false
                }
                VMware.VimAutomation.Core\Remove-VM -DeletePermanently -VM $ComputerName -Confirm:$false
            }
        } -ArgumentList $ComputerName


        if ($PassThru)
        {
            $job
        }
    }
    else
    {
        $vm = VMware.VimAutomation.Core\Get-VM -Name $ComputerName
        if ($vm)
        {
            if ($vm.PowerState -eq "PoweredOn")
            {
                VMware.VimAutomation.Core\Stop-vm -VM $vm -Confirm:$false
            }
            VMware.VimAutomation.Core\Remove-VM -DeletePermanently -VM $ComputerName -Confirm:$false
        }
    }

    Write-LogFunctionExit
}
#endregion Remove-LWVMWareVM

#region Start-LWVMWareVM
function Start-LWVMWareVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$ComputerName,

        [int]$DelayBetweenComputers = 0
    )

    Write-LogFunctionEntry

    foreach ($name in $ComputerName)
    {
        $vm = $null
        $vm = VMware.VimAutomation.Core\Get-VM -Name $name
        if ($vm)
        {
            VMware.VimAutomation.Core\Start-VM $vm -ErrorAction SilentlyContinue | out-null
            $result = VMware.VimAutomation.Core\Get-VM $vm
            if ($result.PowerState -ne "PoweredOn")
            {
                Write-Error "Could not start machine '$name'"
            }
        }
        Start-Sleep -Seconds $DelayBetweenComputers
    }

    Write-LogFunctionExit
}
#endregion Start-LWVMWareVM

#region Save-LWVMWareVM
function Save-LWVMWareVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    Write-LogFunctionEntry

    VMware.VimAutomation.Core\Suspend-VM -VM $ComputerName -ErrorAction SilentlyContinue -Confirm:$false

    Write-LogFunctionExit
}
#endregion Save-LWVMWareVM

#region Stop-LWVMWareVM
function Stop-LWVMWareVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    Write-LogFunctionEntry

    foreach ($name in $ComputerName)
    {
        if (VMware.VimAutomation.Core\Get-VM -Name $name)
        {
            $result = Shutdown-VMGuest -VM $name -ErrorAction SilentlyContinue -Confirm:$false
            if ($result.PowerState -ne "PoweredOff")
            {
                Write-Error "Could not stop machine '$name'"
            }
        }
        else
        {
            Write-ScreenInfo "The machine '$name' does not exist on the connected ESX Server" -Type Warning
        }
    }

    Write-LogFunctionExit
}
#endregion Stop-LWVMWareVM

#region Wait-LWVMWareRestartVM
function Wait-LWVMWareRestartVM
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName,

        [double]$TimeoutInMinutes = 15
    )

    Write-LogFunctionEntry

    $prevErrorActionPreference = $Global:ErrorActionPreference
    $Global:ErrorActionPreference = 'SilentlyContinue'
    $preVerboseActionPreference = $Global:VerbosePreference
    $Global:VerbosePreference = 'SilentlyContinue'

    $start = Get-Date

    Write-PSFMessage "Starting monitoring the servers at '$start'"

    $machines = Get-LabVM -ComputerName $ComputerName

    $cmd = {
        param (
            [datetime]$Start
        )

        $events = Get-EventLog -LogName System -InstanceId 2147489653 -After $Start -Before $Start.AddHours(1)

        $events
    }

    do
    {
        $azureVmsToWait = foreach ($machine in $machines)
        {
            $events = Invoke-LabCommand -ComputerName $machine -ActivityName WaitForRestartEvent -ScriptBlock $cmd -ArgumentList $start.Ticks -UseLocalCredential -PassThru

            if ($events)
            {
                Write-PSFMessage "VM '$machine' has been restarted"
            }
            else
            {
                $machine
            }
            Start-Sleep -Seconds 15
        }
    }
    until ($azureVmsToWait.Count -eq 0 -or (Get-Date).AddMinutes(- $TimeoutInMinutes) -gt $start)

    $Global:ErrorActionPreference = $prevErrorActionPreference
    $Global:VerbosePreference = $preVerboseActionPreference

    if ((Get-Date).AddMinutes(- $TimeoutInMinutes) -gt $start)
    {
        Write-Error -Message "Timeout while waiting for computers to restart. Computers not restarted: $($azureVmsToWait.Name -join ', ')"
    }

    Write-LogFunctionExit
}
#endregion Wait-LWVMWareRestartVM

#region Get-LWVMWareVMStatus
function Get-LWVMWareVMStatus
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    Write-LogFunctionEntry

    $result = @{ }

    foreach ($name in $ComputerName)
    {
        $vm = VMware.VimAutomation.Core\Get-VM -Name $name
        if ($vm)
        {
            if ($vm.PowerState -eq 'PoweredOn')
            {
                $result.Add($vm.Name, 'Started')
            }
            elseif ($vm.PowerState -eq 'PoweredOff')
            {
                $result.Add($vm.Name, 'Stopped')
            }
            else
            {
                $result.Add($vm.Name, 'Unknown')
            }
        }
    }

    $result

    Write-LogFunctionExit
}
#endregion Get-LWVMWareVMStatus

#region Enable-LWVMWareVMRemoting
function Enable-LWVMWareVMRemoting
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCmdlets", "", Justification="Not relevant on Linux")]
    param(
        [Parameter(Mandatory, Position = 0)]
        $ComputerName
    )

    if ($ComputerName)
    {
        $machines = Get-LabVM -All | Where-Object Name -in $ComputerName
    }
    else
    {
        $machines = Get-LabVM -All
    }

    $script = {
        param ($DomainName, $UserName, $Password)

        $VerbosePreference = 'Continue'

        $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 -Role Server -Force | Out-Null
    }

    foreach ($machine in $machines)
    {
        $cred = $machine.GetCredential((Get-Lab))
        try
        {
            Invoke-LabCommand -ComputerName $machine -ActivityName SetLabVMRemoting -ScriptBlock $script `
            -ArgumentList $machine.DomainName, $cred.UserName, $cred.GetNetworkCredential().Password -ErrorAction Stop -Verbose
        }
        catch
        {
            Connect-WSMan -ComputerName $machine -Credential $cred
            Set-Item -Path "WSMan:\$machine\Service\Auth\CredSSP" -Value $true
            Disconnect-WSMan -ComputerName $machine
        }
    }
}
#endregion Enable-LWVMWareVMRemoting