DSCResources/MSFT_xVMScsiController/MSFT_xVMScsiController.psm1

$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath '../../Modules/DscResource.Common'
$script:hyperVDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath '../../Modules/HyperVDsc.Common'

Import-Module -Name $script:dscResourceCommonModulePath
Import-Module -Name $script:hyperVDscCommonModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
    Returns the current status of the VM SCSI controller.
 
    .PARAMETER VMName
    Specifies the name of the virtual machine whose SCSI controller status is to be fetched.
 
    .PARAMETER ControllerNumber
    Specifies the number of the controller to which the hard disk drive is to be set.
    If not specified, the controller number defaults to 0.
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet(0, 1, 2, 3)]
        [System.UInt32]
        $ControllerNumber
    )

    Assert-Module -ModuleName 'Hyper-V'

    $controller = Get-VMScsiController -VMName $VMName -ControllerNumber $ControllerNumber
    if ($null -eq $controller)
    {
        Write-Verbose -Message ($script:localizedData.ControllerNotFound -f $ControllerNumber, $VMName)
        $ensure = 'Absent'
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.ControllerFound -f $ControllerNumber, $VMName)
        $ensure = 'Present'
    }

    return @{
        VMName           = $Controller.VMName
        ControllerNumber = $Controller.ControllerNumber
        RestartIfNeeded  = $false
        Ensure           = $ensure
    }
}

<#
    .SYNOPSIS
    Tests the state of a VM SCSI controller.
 
    .PARAMETER VMName
    Specifies the name of the virtual machine whose SCSI controller is to be tested.
 
    .PARAMETER ControllerNumber
    Specifies the number of the controller to which the hard disk drive is to be set.
    If not specified, the controller number defaults to 0.
 
    .PARAMETER RestartIfNeeded
    Specifies if the VM should be restarted if needed for property changes.
 
    .PARAMETER Ensure
    Specifies if the SCSI controller should exist or not. Default to Present.
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet(0, 1, 2, 3)]
        [System.UInt32]
        $ControllerNumber,

        [Parameter()]
        [System.Boolean]
        $RestartIfNeeded,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    $null = $PSBoundParameters.Remove('RestartIfNeeded')
    $resource = Get-TargetResource -VMName $VMName -ControllerNumber $ControllerNumber

    $isCompliant = $true
    foreach ($key in $resource.Keys)
    {
        Write-Verbose -Message ($script:localizedData.ComparingParameter -f $key,
            $PSBoundParameters[$key],
            $resource[$key])
        $isCompliant = $isCompliant -and ($PSBoundParameters[$key] -eq $resource[$key])
    }

    return $isCompliant
}

<#
    .SYNOPSIS
    Manipulates the state of a VM SCSI controller.
 
    .PARAMETER VMName
    Specifies the name of the virtual machine whose SCSI controller is to be manipulated.
 
    .PARAMETER ControllerNumber
    Specifies the number of the controller to which the hard disk drive is to be set.
    If not specified, the controller number defaults to 0.
 
    .PARAMETER RestartIfNeeded
    Specifies if the VM should be restarted if needed for property changes.
 
    .PARAMETER Ensure
    Specifies if the SCSI controller should exist or not. Defaults to Present.
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet(0, 1, 2, 3)]
        [System.UInt32]
        $ControllerNumber,

        [Parameter()]
        [System.Boolean]
        $RestartIfNeeded,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    Assert-Module -ModuleName 'Hyper-V'

    # Getting the state of the VM so we can restore it later
    $existingVmState = (Get-VMHyperV -VMName $VMName).State

    if ((-not $RestartIfNeeded) -and ($existingVmState -ne 'Off'))
    {
        $errorMessage = $script:localizedData.CannotUpdateVmOnlineError -f $VMName

        New-InvalidOperationException -Message $errorMessage
    }

    [System.Int32] $scsiControllerCount = @(Get-VMScsiController -VMName $VMName).Count
    if ($Ensure -eq 'Present')
    {
        if ($scsiControllerCount -lt $ControllerNumber)
        {
            <#
                All intermediate controllers should be present on the system as we cannot create
                a controller at a particular location. For example, we cannot explicitly create
                controller #2 - it will only be controller #2 if controllers #0 and #1 are already
                added/present in the VM.
            #>

            $errorMessage = $script:localizedData.CannotAddScsiControllerError -f $ControllerNumber

            New-InvalidOperationException -Message $errorMessage
        }

        Set-VMState -Name $VMName -State 'Off'
        Write-Verbose -Message ($script:localizedData.AddingController -f $scsiControllerCount)
        Add-VMScsiController -VMName $VMName
    }
    else
    {
        if ($scsiControllerCount -ne ($ControllerNumber + 1))
        {
            <#
                All intermediate controllers should be present on the system. Whilst we can remove
                a controller at a particular location, all remaining controller numbers may be
                reordered. For example, if we remove controller at position #1, then a controller
                that was at position #2 will become controller number #1.
            #>

            $errorMessage = $script:localizedData.CannotRemoveScsiControllerError -f $ControllerNumber

            New-InvalidOperationException -Message $errorMessage
        }

        Set-VMState -Name $VMName -State 'Off'
        Write-Verbose -Message ($script:localizedData.CheckingExistingDisks -f $ControllerNumber)
        $controller = Get-VMScsiController -VMName $VmName -ControllerNumber $ControllerNumber

        foreach ($drive in $controller.Drives)
        {
            $warningMessage = $script:localizedData.RemovingDiskWarning -f $drive.Path, $ControllerNumber
            Write-Warning -Message $warningMessage
            Remove-VMHardDiskDrive -VMHardDiskDrive $drive
        }

        Write-Verbose -Message ($script:localizedData.RemovingController -f $ControllerNumber, $VMName)
        Remove-VMScsiController -VMScsiController $controller
    }

    # Restore the previous state
    Set-VMState -Name $VMName -State $existingVmState
}

Export-ModuleMember -Function *-TargetResource