DSCResources/POSHOrigin_vSphere_VM/POSHOrigin_vSphere_VM.psm1

# Load helpers
Get-ChildItem -Path "$psscriptroot\Helpers" | ForEach-Object {
    Write-Debug -Message "Loading helper function $($_.Name)"
    . $_.FullName
}

$script:VCConnected = $false

function Get-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,

        [System.Boolean]
        $PowerOnAfterCreation,

        [System.Management.Automation.PSCredential]
        $vCenterCredentials,

        [System.Int32]
        $TotalvCPU,

        [System.Int32]
        $CoresPerSocket,

        [System.Int32]
        $vRAM,

        [System.String]
        $Disks,

        [System.String]
        $Networks,

        [System.String]
        $VMTemplate,

        [System.String]
        $VMFolder,

        [System.String]
        $CustomizationSpec,

        [System.String]
        $Tags,

        [System.String]
        $Description,

        [System.Boolean]
        $UpdateTools = $false,

        [System.Management.Automation.PSCredential]
        $GuestCredentials,

        [System.Management.Automation.PSCredential]
        $IPAMCredentials,

        [System.Management.Automation.PSCredential]
        $DomainJoinCredentials,

        [System.String]
        $IPAMFqdn,

        [System.String]
        $vCenter,

        [System.String]
        $Datacenter,

        [System.String]
        $InitialDatastore,

        [System.String]
        $Cluster,

        [System.String]
        $ResourcePool,

        [System.String]
        $VMHost,

        [System.String]
        $vApp,

        [System.String]
        $Provisioners
    )

    $returnValue = @{
        Name = $Name
        Ensure = $Ensure
        PowerOnAfterCreation = $PowerOnAfterCreation
        vCenterCredentials = $vCenterCredentials
        TotalvCPU = $TotalvCPU
        CoresPerSocket = $CoresPerSocket
        vRAM = $vRAM
        Disks = $Disks
        Networks = $Networks
        VMTemplate = $VMTemplate
        CustomizationSpec = $CustomizationSpec
        GuestCredentials = $GuestCredentials
        IPAMCredentials = $IPAMCredentials
        IPAMFqdn = $IPAMFqdn
        vCenter = $vCenter
        Datacenter =$Datacenter
        InitialDatastore = $InitialDatastore
        Cluster = $Cluster
        ResourcePool = $ResourcePool
        VMHost = $VMHost
        Tags = $Tags
        Description = $Description
        UpdateTools = $UpdateTools
        vApp = $vApp
        Provisioners = $Provisioners
    }

    $returnValue
}

function Set-TargetResource {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,

        [System.Boolean]
        $PowerOnAfterCreation,

        [System.Management.Automation.PSCredential]
        $vCenterCredentials,

        [System.Int32]
        $TotalvCPU,

        [System.Int32]
        $CoresPerSocket,

        [System.Int32]
        $vRAM,

        [System.String]
        $Disks,

        [System.String]
        $Networks,

        [System.String]
        $VMTemplate,

        [System.String]
        $VMFolder,

        [System.String]
        $CustomizationSpec,

        [System.String]
        $Tags,

        [System.String]
        $Description,

        [System.Boolean]
        $UpdateTools = $false,

        [System.Management.Automation.PSCredential]
        $GuestCredentials,

        [System.Management.Automation.PSCredential]
        $IPAMCredentials,

        [System.Management.Automation.PSCredential]
        $DomainJoinCredentials,

        [System.String]
        $IPAMFqdn,

        [System.String]
        $vCenter,

        [System.String]
        $Datacenter,

        [System.String]
        $InitialDatastore,

        [System.String]
        $Cluster,

        [System.String]
        $ResourcePool,

        [System.String]
        $VMHost,

        [System.String]
        $vApp,

        [System.String]
        $Provisioners
    )

    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1

    try {
        # Connect to vCenter
        $script:VCConnected = _ConnectTovCenter -vCenter $vCenter -Credential $vCenterCredentials

        $newVM = $false
        $vm = Get-VM -Name $Name -Verbose:$false -ErrorAction SilentlyContinue | Select-Object -First 1

        if ($Ensure -eq 'Present') {

            # Create the VM if needed
            if ($null -eq $vm) {

                $diskSpec = ConvertFrom-Json -InputObject $Disks
                $format = $diskSpec[0].Format

                $continue = $true

                # Let's validate that only ONE of the following options was specified for the VM location
                # Cluster, ResourcePool, VMHost, vApp
                $search = @('Cluster', 'ResourcePool', 'VMHost', 'vApp')
                if (($PSBoundParameters.Keys | Where-Object {$_ -in $search}).Count -gt 1) {
                    $continue = $false
                    $msg = "More than one option was specified for VM location. Please choose ONE of the following options"
                    $msg += "Cluster, ResourcePool, VMHost, or vApp"
                    Write-Error -Message $msg
                }

                if ($continue) {
                    $params = @{
                        Name = $Name
                        VMTemplate = $VMTemplate
                        InitialDatastore = $InitialDatastore
                        DiskFormat = $format
                        NICSpec = $Networks
                        CustomizationSpec = $CustomizationSpec
                        #IPAMFqdn = $IPAMFqdn
                        #IPAMCredentials = $IPAMCredentials
                    }
                    if ($PSBoundParameters.ContainsKey('VMFolder')) {
                        $params.Folder = $VMFolder
                    }
                    if ($PSBoundParameters.ContainsKey('Cluster')) {
                        $params.Cluster = $Cluster
                    }
                    if ($PSBoundParameters.ContainsKey('ResourcePool')) {
                        $params.ResourcePool = $ResourcePool
                    }
                    if ($PSBoundParameters.ContainsKey('VMHost')) {
                        $params.VMHost = $VMHost
                    }
                    if ($PSBoundParameters.ContainsKey('vApp')) {
                        $params.vApp = $vApp
                    }

                    $vm = _CreateVM @params
                    if ($null -ne $vm) {
                        Write-Verbose -Message 'VM created successfully'
                        $newVm = $true
                    } else {
                        return
                    }

                    # Set NICs
                    $setNICResult = $false
                    $setNICResult = _SetVMNICs -vm $vm -NICSpec $Networks -CustomizationSpec $CustomizationSpec -IPAMFqdn $IPAMFqdn -IPAMCredentials $IPAMCredentials

                    if ($setNICResult -eq $false) {
                        throw 'Failed to set NICs after VM creation. Aborting...'
                    }
                }
            }

            if ($vm) {
                # Set Description
                if (-not (_TestVMDescription -vm $VM -Description $Description)) {
                    _SetVMDescription -VM $vm -Description $Description
                }

                # Set RAM
                if (-not (_TestVMRAM -vm $vm -RAM $vRAM)) {
                    _SetVMRAM -vm $vm -RAM $vRAM
                }

                # Set vCPU
                if (-not (_TestVMCPU -vm $vm -TotalvCPU $TotalvCPU -CoresPerSocket $CoresPerSocket)) {
                    _SetVMCPU -vm $vm -TotalvCPU $TotalvCPU -CoresPerSocket $CoresPerSocket
                }

                # Set VM Folder
                if ($PSBoundParameters.ContainsKey('VMFolder')) {
                    if (-Not (_TestVMFolder -VM $VM -VMFolder $VMFolder)) {
                        _MoveVM -VM $VM -VMFolder $VMFolder
                    }
                }

                # Set tags
                if ($PSBoundParameters.ContainsKey('Tags')) {
                    if (-not (_TestTags -VM $VM -Tags $Tags)) {
                        _SetTags -VM $VM -Tags $Tags
                    }
                }

                # Set disks
                if (-not (_TestVMDisks -vm $vm -DiskSpec $Disks)) {
                    $updatedVMDisks = _SetVMDisks -vm $vm -DiskSpec $Disks
                }

                # Set NICS
                # TODO

                # Power on VM and wait for OS customization to complete
                if (-not (_TestVMPowerState -vm $vm -PowerOnAfterCreation $PowerOnAfterCreation)) {
                    _SetVMPowerState -vm $vm

                    # Wait for OS customization to complete if this is a newly created VM
                    if ($newVM -eq $true) {
                        _WaitForGuestCustomization -vm $vm
                    }

                    _WaitForVMTools -vm $vm -Credential $GuestCredentials
                }

                # Guest disks
                $vm = Get-VM -Name $Name -Verbose:$false -ErrorAction SilentlyContinue | Select-Object -First 1
                if ($VM.PowerState -eq 'PoweredOn') {

                    # Establish CIM/PS remoting sessions for subsequent checks
                    $ip = _GetGuestVMIPAddress -VM $vm
                    $cimSession = _NewCIMSession -IPAddress $ip -Credential $GuestCredentials
                    $psSession = _NewPSSession -IPAddress $ip -Credential $GuestCredentials

                    if ($updatedVMDisks -eq $true) {
                        _refreshHostStorageCache -PSSession $psSession
                    }

                    # Set guest disks
                    $guestDiskParams = @{
                        VM = $vm
                        DiskSpec = $Disks
                        CimSession = $cimSession
                        PSSession = $psSession
                    }
                    if (-not (_TestGuestDisks @guestDiskParams )) {
                        _SetGuestDisks @guestDiskParams
                    }
                } else {
                    Write-Warning -Message 'VM is powered off. Skipping guest check'
                }
            }

            # Set VM Folder
            if ($VMFolder -ne [string]::empty) {
                if (-Not (_TestVMFolder -VM $VM -VMFolder $VMFolder)) {
                    _MoveVM -VM $VM -VMFolder $VMFolder
                }
            }

            # VM Tools
            if ($UpdateTools) {
                if (-not (_TestVMTools -VM $VM)) {
                    _UpdateTools -VM $VM
                }
            }

            # Run any provisioners
            if ($Provisioners -ne [string]::Empty) {
                foreach ($p in (ConvertFrom-Json -InputObject $Provisioners)) {
                    $testPath = "$PSScriptRoot\Provisioners\$($p.name)\Test.ps1"
                    if (Test-Path -Path $testPath) {

                        $params = $PSBoundParameters
                        $params.vm = $vm
                        $params.ProvOptions = $p.options

                        $provisionerResult = (& $testPath $params)
                        if ($provisionerResult -ne $true) {
                            $provPath = "$PSScriptRoot\Provisioners\$($p.name)\Provision.ps1"
                            if (Test-Path -Path $testPath) {
                                $params = $PSBoundParameters
                                $params.vm = $vm
                                (& $provPath $params)
                            }
                        }
                    }
                }
            }
        } else {
            Write-Verbose '[Ensure == Absent] Beginning deprovisioning process'

            # Run through any provisioners we have defined and execute the 'deprovision' script
            if ($PSBoundParameters.ContainsKey('Provisioners')) {
                foreach ($p in (ConvertFrom-Json -InputObject $Provisioners)) {
                    $testPath = "$PSScriptRoot\Provisioners\$($p.name)\Test.ps1"
                    if (Test-Path -Path $testPath) {
                        $params = $PSBoundParameters
                        $params.vm = $vm
                        $params.ProvOptions = $p.options
                        $provisionerResult = (& $testPath $params)
                        if ($provisionerResult -eq $true) {
                            $provPath = "$PSScriptRoot\Provisioners\$($p.name)\Deprovision.ps1"
                            if (Test-Path -Path $testPath) {
                                $params = $PSBoundParameters
                                $params.vm = $vm
                                (& $provPath $params)
                            }
                        }
                    }
                }
            }

            # Connect to vCenter
            $script:VCConnected = _ConnectTovCenter -vCenter $vCenter -Credential $vCenterCredentials

            # Stop and delete VM
            $vm = Get-VM -Name $Name -Verbose:$false -ErrorAction SilentlyContinue | Select-Object -First 1
            if ($null -ne $vm) {
                try {
                    if ($vm.PowerState -eq 'PoweredOn') {
                        Write-Verbose -Message 'Stopping VM'
                        $stopResult = $vm | Stop-VM -Confirm:$false -Verbose:$false
                        Write-Verbose -Message 'VM stopped'
                    }
                    if ($vm.PowerState -eq 'PoweredOff' -or $stopResult.PowerState -eq 'PoweredOff') {
                        $delResult = $vm | Remove-VM -DeletePermanently -Confirm:$false -Verbose:$false
                        Write-Verbose -Message 'VM deleted'
                    } else {
                        throw 'Unable to stop VM'
                    }
                } catch {
                    throw $_
                }
            }
        }
    } catch {
        Write-Error 'There was a problem setting the resource'
        Write-Error "$($_.InvocationInfo.ScriptName)($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)"
        Write-Error $_
    }

    # Cleanup connections
    $cimSession | Remove-CimSession -Verbose:$false -ErrorAction SilentlyContinue
    $psSession | Remove-PSSession -Verbose:$false -ErrorAction SilentlyContinue
    _DisconnectFromvCenter -vCenter $vCenter
}

function Test-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,

        [System.Boolean]
        $PowerOnAfterCreation,

        [System.Management.Automation.PSCredential]
        $vCenterCredentials,

        [System.Int32]
        $TotalvCPU,

        [System.Int32]
        $CoresPerSocket,

        [System.Int32]
        $vRAM,

        [System.String]
        $Disks,

        [System.String]
        $Networks,

        [System.String]
        $VMTemplate,

        [System.String]
        $VMFolder,

        [System.String]
        $CustomizationSpec,

        [System.String]
        $Tags,

        [System.String]
        $Description,

        [System.Boolean]
        $UpdateTools = $false,

        [System.Management.Automation.PSCredential]
        $GuestCredentials,

        [System.Management.Automation.PSCredential]
        $IPAMCredentials,

        [System.Management.Automation.PSCredential]
        $DomainJoinCredentials,

        [System.String]
        $IPAMFqdn,

        [System.String]
        $vCenter,

        [System.String]
        $Datacenter,

        [System.String]
        $InitialDatastore,

        [System.String]
        $Cluster,

        [System.String]
        $ResourcePool,

        [System.String]
        $VMHost,

        [System.String]
        $vApp,

        [System.String]
        $Provisioners
    )

    # Connect to vCenter
    $script:VCConnected = _ConnectTovCenter -vCenter $vCenter -Credential $vCenterCredentials

    $result = $true

    # Check if VM exists
    $vm = Get-VM -Name $Name -verbose:$false -ErrorAction SilentlyContinue | select -First 1
    if ($vm -ne $null) {
        Write-Verbose -Message "VM exists"
    } else {
        Write-Verbose -Message "VM does not exist"
    }

    # If VM exists, is it supposed to?
    if ($Ensure -eq 'Present') {
        if (-Not $vm) {
            Write-Information -Message "$Name should exist but doesn't"
            return $false
        }
    } else {
        if (-Not $vm) {
            Write-Information -Message "$Name doesn't exist and should not"
            return $true
        } else {
            Write-Information -Message "$Name shouldn't exist but does"
            return $false
        }
    }

    #region Run through tests
    # Description
    $descriptionResult = _TestVMDescription -VM $vm -Description $Description
    $match = if ($descriptionResult) { 'MATCH'} else { 'MISMATCH' }
    Write-Verbose -Message "Description: $match"

    # RAM
    $ramResult = _TestVMRAM -VM $vm -RAM $vRAM
    $match = if ( $ramResult) { 'MATCH' } else { 'MISMATCH' }
    Write-Verbose -Message "RAM: $match"

    # CPU
    $cpuResult = _TestVMCPU -vm $vm -TotalvCPU $TotalvCPU -CoresPerSocket $CoresPerSocket
    $match = if ( $cpuResult) { 'MATCH' } else { 'MISMATCH' }
    Write-Verbose -Message "vCPU: $match"

    # Test VM folder
    $folderResult = $true
    if ($PSBoundParameters.ContainsKey('VMFolder')) {
        $folderResult = _TestVMFolder -VM $vm -VMFolder $VMFolder
        $match = if ( $folderResult) { 'MATCH' } else { 'MISMATCH' }
        Write-Verbose -Message "VM Folder: $match"
    }

    # Tags
    $tagResult = $true
    if ($PSBoundParameters.ContainsKey('Tags')) {
        $tagResult = _TestTags -VM $VM -Tags $Tags
    }
    $match = if ( $tagResult) { 'MATCH' } else { 'MISMATCH' }
    Write-Verbose -Message "Tags: $match"

    # Disks
    $vmDiskResult = _TestVMDisks -vm $vm -DiskSpec $Disks
    $match = if ( $vmDiskResult) { 'MATCH' } else { 'MISMATCH' }
    Write-Verbose -Message "VM Disks: $match"

    # NICs
    # TODO

    # Establish CIM/PS remoting sessions for subsequent checks
    $ip = _GetGuestVMIPAddress -VM $vm
    if ([string]::IsNullOrEmpty($ip)) {
        throw 'Unable to retreive valid guest IP address'
    }

    Write-Verbose -Message "Guest IP address: $ip"
    $cimSession = _NewCIMSession -IPAddress $ip -Credential $GuestCredentials
    $psSession = _NewPSSession -IPAddress $ip -Credential $GuestCredentials

    # Guest disks
    $guestDiskResult = $true
    if ($VM.PowerState -eq 'PoweredOn') {
        #_refreshHostStorageCache -vm $vm -Credential $GuestCredentials
        _refreshHostStorageCache -PSSession $psSession
        #$guestDiskResult = _TestGuestDisks -vm $vm -DiskSpec $Disks -Credential $GuestCredentials
        $guestDiskResult = _TestGuestDisks -vm $vm -DiskSpec $Disks -CimSession $CimSession -PSSession $PSSession
        $match = if ( $guestDiskResult) { 'MATCH' } else { 'MISMATCH' }
        Write-Verbose -Message "Guest disks: $match"
    } else {
        Write-Warning -Message 'VM is powered off. Skipping guest disk check'
    }

    # NICs
    # TODO

    # Test VM folder
    $folderResult = $true
    if ($VMFolder -ne [string]::Empty) {
        $folderResult = _TestVMFolder -VM $vm -VMFolder $VMFolder
        $match = if ( $folderResult) { 'MATCH' } else { 'MISMATCH' }
        Write-Verbose -Message "VM Folder: $match"
    }

    # Power state
    $powerResult = _TestVMPowerState -vm $vm -PowerOnAfterCreation $PowerOnAfterCreation
    $match = if ( $powerResult) { 'MATCH' } else { 'MISMATCH' }
    Write-Verbose -Message "Power state: $match"

    # VM Tools
    if ($UpdateTools) {
        $toolsResult = _TestVMTools -VM $VM
        $match = if ( $toolsResult) { 'Current' } else { 'OUT OF DATE/NOT INSTALLED' }
        Write-Verbose -Message "VM Tools: $match"
    } else {
        $toolsResult = $true
    }

    #endregion

    # Test provisioners
    $provisionerResults = @()
    if ($PSBoundParameters.ContainsKey('Provisioners')) {
        foreach ($p in (ConvertFrom-Json -InputObject $Provisioners)) {
            $provPath = "$PSScriptRoot\Provisioners\$($p.name)\Test.ps1"
            if (Test-Path -Path $provPath) {
                $params = $PSBoundParameters
                $params.vm = $vm
                $params.ProvOptions = $p.options
                $provisionerPassed = (& $provPath $params)
                $provisionerResults += $provisionerPassed
                #if (-not $provisionerPassed) {
                # return $false
                #}
            }
        }
    }

    # Cleanup connections
    $cimSession | Remove-CimSession -Verbose:$false -ErrorAction SilentlyContinue
    $psSession | Remove-PSSession -Verbose:$false -ErrorAction SilentlyContinue
    _DisconnectFromvCenter -vCenter $vCenter

    if (-not ($descriptionResult -and $ramResult -and $cpuResult -and $vmDiskResult -and $guestDiskResult -and $powerResult -and $folderResult -and $tagResult -and $toolsResult)) {
        Write-Debug -Message "One or more tests failed"
        return $false
    }

    Write-Debug -Message 'Provisioner results:'
    Write-Debug -Message ($provisionerResults | Format-List | Out-String)

    if (($provisionerResults | Where-Object {$_ -ne $true }).Count -gt 0) {
        Write-Verbose -Message "One or more provisioners failed tests"
        return $false
    }

    return $true
}

Export-ModuleMember -Function *-TargetResource