DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the Storage Common Module.
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'StorageDsc.Common' `
            -ChildPath 'StorageDsc.Common.psm1'))

# Import the VirtualHardDisk Win32Helpers Module.
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'StorageDsc.VirtualHardDisk.Win32Helpers' `
            -ChildPath 'StorageDsc.VirtualHardDisk.Win32Helpers.psm1'))

Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common')

# Import Localization Strings.
$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
        Returns the current state of the virtual hard disk.
 
    .PARAMETER FilePath
        Specifies the complete path to the virtual hard disk file.
#>

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

    Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($script:localizedData.GettingVirtualHardDisk -f $FilePath)
        ) -join '' )

    $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue
    $ensure = 'Present'

    if (-not $diskImage)
    {
        $ensure = 'Absent'
    }

    # Get the virtual hard disk info using its path on the system
    return @{
        FilePath   = $diskImage.ImagePath
        Attached   = $diskImage.Attached
        DiskSize   = $diskImage.Size
        DiskNumber = $diskImage.DiskNumber
        Ensure     = $ensure
    }
} # function Get-TargetResource

<#
    .SYNOPSIS
        Returns the current state of the virtual hard disk.
 
    .PARAMETER FilePath
        Specifies the complete path to the virtual hard disk file.
 
    .PARAMETER DiskSize
        Specifies the size of new virtual hard disk.
 
    .PARAMETER DiskFormat
        Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported.
 
    .PARAMETER DiskType
        Specifies the supported virtual hard disk type.
 
    .PARAMETER Ensure
        Determines whether the setting should be applied or removed.
#>

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

        [Parameter()]
        [ValidateScript({ $_ -gt 0 })]
        [System.UInt64]
        $DiskSize,

        [Parameter()]
        [ValidateSet('Vhd', 'Vhdx')]
        [System.String]
        $DiskFormat,

        [Parameter()]
        [ValidateSet('Fixed', 'Dynamic')]
        [System.String]
        $DiskType = 'Dynamic',

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

    Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat

    try
    {
        Assert-ElevatedUser
    }
    catch
    {
        # Use a user friendly error message specific to the virtual disk dsc resource.
        throw $script:localizedData.VirtualDiskAdminError
    }

    $currentState = Get-TargetResource -FilePath $FilePath

    if ($Ensure -eq 'Present')
    {
        # Disk doesn't exist
        if (-not $currentState.FilePath)
        {
            Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($script:localizedData.VirtualHardDiskDoesNotExistCreatingNow -f $FilePath)
                ) -join '' )

            $folderPath = Split-Path -Parent $FilePath
            $wasLocationCreated = $false

            try
            {
                # Create the location if it doesn't exist.
                if (-not (Test-Path -PathType Container $folderPath))
                {
                    New-Item -ItemType Directory -Path $folderPath
                    $wasLocationCreated = $true
                }

                New-SimpleVirtualDisk -VirtualDiskPath $FilePath -DiskFormat $DiskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize
            }
            catch
            {
                # Remove file if we created it but were unable to attach it. No handles are open when this happens.
                if (Test-Path -Path $FilePath -PathType Leaf)
                {
                    Write-Verbose -Message ($script:localizedData.RemovingCreatedVirtualHardDiskFile -f $FilePath)
                    Remove-Item -LiteralPath $FilePath -Verbose -Force
                }

                if ($wasLocationCreated)
                {
                    Remove-Item -LiteralPath $folderPath -Verbose -Force
                }

                # Rethrow the exception
                throw
            }

        }
        elseif (-not $currentState.Attached)
        {
            Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($script:localizedData.VirtualDiskNotMounted -f $FilePath)
                ) -join '' )

            # Virtual hard disk file exists so lets attempt to attach it to the system.
            Add-SimpleVirtualDisk -VirtualDiskPath $FilePath -DiskFormat $DiskFormat
        }
    }
    else
    {
        # Detach the virtual hard disk if its not supposed to be attached.
        if ($currentState.Attached)
        {
            Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($script:localizedData.VirtualHardDiskDetachingImage `
                            -f $FilePath)
                ) -join '' )

            Dismount-DiskImage -ImagePath $FilePath
        }
    }
} # function Set-TargetResource

<#
    .SYNOPSIS
        Returns the current state of the virtual hard disk.
 
    .PARAMETER FilePath
        Specifies the complete path to the virtual hard disk file.
 
    .PARAMETER DiskSize
        Specifies the size of new virtual hard disk.
 
    .PARAMETER DiskFormat
        Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported.
 
    .PARAMETER DiskType
        Specifies the supported virtual hard disk type.
 
    .PARAMETER Ensure
        Determines whether the setting should be applied or removed.
#>

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

        [Parameter()]
        [ValidateScript({ $_ -gt 0 })]
        [System.UInt64]
        $DiskSize,

        [Parameter()]
        [ValidateSet('Vhd', 'Vhdx')]
        [System.String]
        $DiskFormat,

        [Parameter()]
        [ValidateSet('Fixed', 'Dynamic')]
        [System.String]
        $DiskType = 'Dynamic',

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

    Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat

    $currentState = Get-TargetResource -FilePath $FilePath

    Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($script:localizedData.CheckingVirtualDiskExists -f $FilePath)
        ) -join '' )

    if ($Ensure -eq 'Present')
    {
        # Found the virtual hard disk and confirmed its attached to the system.
        if ($currentState.Attached)
        {
            Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($script:localizedData.VirtualHardDiskCurrentlyMounted -f $FilePath)
                ) -join '' )

            return $true
        }

        Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($script:localizedData.VirtualHardDiskMayNotExistOrNotMounted -f $FilePath)
            ) -join '' )

        return $false
    }
    else
    {
        # Found the virtual hard disk and confirmed its attached to the system but ensure variable set to 'Absent'.
        if ($currentState.Attached)
        {
            Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($script:localizedData.VirtualHardDiskCurrentlyMountedButShouldNotBe -f $FilePath)
                ) -join '' )

            return $false
        }

        Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($script:localizedData.VirtualHardDiskMayNotExistOrNotMounted -f $FilePath)
            ) -join '' )

        return $true
    }
} # function Test-TargetResource

<#
    .SYNOPSIS
        Validates parameters for both set and test operations.
 
    .PARAMETER FilePath
        Specifies the complete path to the virtual hard disk file.
 
    .PARAMETER DiskSize
        Specifies the size of new virtual hard disk.
 
    .PARAMETER DiskFormat
        Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported.
#>

function Assert-ParametersValid
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $FilePath,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_ -gt 0 })]
        [System.UInt64]
        $DiskSize,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Vhd', 'Vhdx')]
        [System.String]
        $DiskFormat
    )

    # We'll only support local paths with drive letters.
    if ($FilePath -notmatch '[a-zA-Z]:\\')
    {
        # AccessPath is invalid
        New-InvalidArgumentException `
            -Message $($script:localizedData.VirtualHardDiskPathError -f $FilePath) `
            -ArgumentName 'FilePath'
    }

    $extension = [System.IO.Path]::GetExtension($FilePath).TrimStart('.')
    if ($extension)
    {
        if (($extension -ne 'vhd') -and ($extension -ne 'vhdx'))
        {
            New-InvalidArgumentException `
                -Message $($script:localizedData.VirtualHardDiskUnsupportedFileType -f $extension) `
                -ArgumentName 'FilePath'
        }
        elseif ($extension -ne $DiskFormat)
        {
            New-InvalidArgumentException `
                -Message $($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) `
                -ArgumentName 'FilePath'
        }
    }
    else
    {
        New-InvalidArgumentException `
            -Message $($script:localizedData.VirtualHardDiskNoExtensionError -f $FilePath) `
            -ArgumentName 'FilePath'
    }

    <#
        Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for
        the vhd format in 2040GB. Maximum for the vhdx format is 64TB.
    #>

    $isVhdxFormat = $DiskFormat -eq 'Vhdx'
    $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB)
    $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB)
    if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor
        ($isVhdxFormat -and $isInValidSizeForVhdxFormat))
    {
        if ($DiskSize -lt 1GB)
        {
            $diskSizeString = ($DiskSize / 1MB).ToString('0.00MB')
        }
        else
        {
            $diskSizeString = ($DiskSize / 1TB).ToString('0.00TB')
        }

        $invalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalid
        if ($isVhdxFormat)
        {
            $invalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalid
        }

        New-InvalidArgumentException `
            -Message $($invalidSizeMsg -f $diskSizeString) `
            -ArgumentName 'DiskSize'
    }
} # Assert-ParametersValid

Export-ModuleMember -Function *-TargetResource