Src/LabImage.ps1

function Get-LabImage {
<#
    .SYNOPSIS
        Gets master/parent disk image.
    .DESCRIPTION
        The Get-LabImage cmdlet returns current master/parent disk image properties.
    .PARAMETER Id
        Specifies the media Id of the image to return. If this parameter is not specified, all images are returned.
    .PARAMETER ConfigurationData
        Specifies a PowerShell DSC configuration data hashtable or a path to an existing PowerShell DSC .psd1
        configuration document that contains the required media definition.
    .EXAMPLE
        Get-LabImage
 
        Returns all current lab images on the host.
    .EXAMPLE
        Get-LabImage -Id 2012R2_x64_Standard_EN_Eval
 
        Returns the '2012R2_x64_Standard_EN_Eval' lab image properties, if available.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param (
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()]
        [System.String] $Id,

        ## Lab DSC configuration data
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Collections.Hashtable]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        $ConfigurationData
    )
    process {
        $hostDefaults = GetConfigurationData -Configuration Host;
        $parentVhdPath = ResolvePathEx -Path $hostDefaults.ParentVhdPath;

        if ($PSBoundParameters.ContainsKey('Id')) {
            ## We have an Id. so resolve that
            try {
                $labMedia = ResolveLabMedia @PSBoundParameters;
            }
            catch {
                $labMedia = $null;
            }
        }
        else {
            ## Otherwise return all media
            $labMedia = Get-LabMedia;
        }

        foreach ($media in $labMedia) {
            $differencingVhdPath = '{0}.vhdx' -f $media.Id;
            if ($media.MediaType -eq 'VHD') {
                $differencingVhdPath = $media.Filename;
            }

            $imagePath = Join-Path -Path $parentVhdPath -ChildPath $differencingVhdPath;
            if (Test-Path -Path $imagePath -PathType Leaf) {
                $imageFileInfo = Get-Item -Path $imagePath;
                $diskImage = Get-DiskImage -ImagePath $imageFileInfo.FullName;
                $labImage = [PSCustomObject] @{
                    Id = $media.Id;
                    Attached = $diskImage.Attached;
                    ImagePath = $diskImage.ImagePath;
                    LogicalSectorSize = $diskImage.LogicalSectorSize;
                    BlockSize = $diskImage.BlockSize;
                    FileSize = $diskImage.FileSize;
                    Size = $diskImage.Size;
                    Generation = ($imagePath.Split('.')[-1]).ToUpper();
                }
                Write-Output -InputObject $labImage;
            }
        } #end foreach media
    } #end process
} #end function Get-LabImage

function Test-LabImage {
<#
    .SYNOPSIS
        Tests whether a master/parent lab image is present.
    .DESCRIPTION
        The Test-LabImage cmdlet returns whether a specified disk image is present.
    .PARAMETER Id
        Specifies the media Id of the image to test.
    .PARAMETER ConfigurationData
        Specifies a PowerShell DSC configuration data hashtable or a path to an existing PowerShell DSC .psd1
        configuration document that contains the required media definition.
    .EXAMPLE
        Test-LabImage -Id 2012R2_x64_Standard_EN_Eval
 
        Tests whether the '-Id 2012R2_x64_Standard_EN_Eval' lab image is present.
    .LINK
        Get-LabImage
#>

    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()]
        [System.String] $Id,

        ## Lab DSC configuration data
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Collections.Hashtable]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        $ConfigurationData
    )
    process {
        if (Get-LabImage @PSBoundParameters) {
            return $true;
        }
        else {
            return $false;
        }
    } #end process
} #end function Test-LabImage

function New-LabImage {
<#
    .SYNOPSIS
        Creates a new master/parent lab image.
    .DESCRIPTION
        The New-LabImage cmdlet starts the creation of a lab VHD(X) master image from the specified media Id.
 
        Lability will automatically create lab images as required. If there is a need to manally recreate an image,
        then the New-LabImage cmdlet can be used.
    .PARAMETER Id
        Specifies the media Id of the image to create.
    .PARAMETER ConfigurationData
        Specifies a PowerShell DSC configuration data hashtable or a path to an existing PowerShell DSC .psd1
        configuration document that contains the required media definition.
    .PARAMETER Force
        Specifies that any existing image should be overwritten.
    .EXAMPLE
        New-LabImage -Id 2012R2_x64_Standard_EN_Eval
 
        Creates the VHD(X) image from the '2012R2_x64_Standard_EN_Eval' media Id.
    .EXAMPLE
        New-LabImage -Id 2012R2_x64_Standard_EN_Eval -Force
 
        Creates the VHD(X) image from the '2012R2_x64_Standard_EN_Eval' media Id, overwriting an existing image with the same name.
    .LINK
        Get-LabMedia
        Get-LabImage
#>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.IO.FileInfo])]
    param (
        ## Lab media Id
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()]
        [System.String] $Id,

        ## Lab DSC configuration data
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Collections.Hashtable]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        $ConfigurationData,

        ## Force the re(creation) of the master/parent image
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force
    )
    process {
        ## Download media if required..
        [ref] $null = $PSBoundParameters.Remove('Force');
        [ref] $null = $PSBoundParameters.Remove('WhatIf');
        [ref] $null = $PSBoundParameters.Remove('Confirm');

        if ((Test-LabImage @PSBoundParameters) -and $Force) {
            $image = Get-LabImage @PSBoundParameters;
            WriteVerbose ($localized.RemovingDiskImage -f $image.ImagePath);
            [ref] $null = Remove-Item -Path $image.ImagePath -Force -ErrorAction Stop;
        }
        elseif (Test-LabImage @PSBoundParameters) {
            throw ($localized.ImageAlreadyExistsError -f $Id);
        }

        $media = ResolveLabMedia @PSBoundParameters;
        $mediaFileInfo = InvokeLabMediaImageDownload -Media $media;
        $hostDefaults = GetConfigurationData -Configuration Host;

        if ($media.MediaType -eq 'VHD') {
            WriteVerbose ($localized.ImportingExistingDiskImage -f $media.Description);
            $imageName = $media.Filename;
            $imagePath = Join-Path -Path $hostDefaults.ParentVhdPath -ChildPath $imageName;
        } #end if VHD
        else {
            WriteVerbose ($localized.CreatingDiskImage -f $media.Description);
            $imageName = '{0}.vhdx' -f $Id;
            $imagePath = Join-Path -Path $hostDefaults.ParentVhdPath -ChildPath $imageName;
            $imageCreationFailed = $false;

            try {
                ## Create VHDX
                if ($media.CustomData.PartitionStyle) {
                    ## Custom partition style has been defined so use that
                    $partitionStyle = $media.CustomData.PartitionStyle;
                }
                elseif ($media.Architecture -eq 'x86') {
                    ## Otherwise default to MBR for x86 media
                    $partitionStyle = 'MBR';
                }
                else {
                    $partitionStyle = 'GPT';
                }

                ## Create disk image and refresh PSDrives
                $image = NewDiskImage -Path $imagePath -PartitionStyle $partitionStyle -Passthru -Force -ErrorAction Stop;
                [ref] $null = Get-PSDrive;

                ## Apply WIM (ExpandWindowsImage) and add specified features
                $expandWindowsImageParams = @{
                    Vhd = $image;
                    MediaPath = $mediaFileInfo.FullName;
                    PartitionStyle = $partitionStyle;
                }

                ## Determine whether we're using the WIM image index or image name. This permits
                ## specifying an integer image index in a media's 'ImageName' property.
                [System.Int32] $wimImageIndex = $null;
                if ([System.Int32]::TryParse($media.ImageName, [ref] $wimImageIndex)) {
                    $expandWindowsImageParams['WimImageIndex'] = $wimImageIndex;
                }
                else {
                    $expandWindowsImageParams['WimImageName'] = $media.ImageName;
                }

                if ($media.CustomData.SourcePath) {
                    $expandWindowsImageParams['SourcePath'] = $media.CustomData.SourcePath;
                }
                if ($media.CustomData.WimPath) {
                    $expandWindowsImageParams['WimPath'] = $media.CustomData.WimPath;
                }
                if ($media.CustomData.WindowsOptionalFeature) {
                    $expandWindowsImageParams['WindowsOptionalFeature'] = $media.CustomData.WindowsOptionalFeature;
                }
                if ($media.CustomData.PackagePath) {
                    $expandWindowsImageParams['PackagePath'] = $media.CustomData.PackagePath;
                }
                if ($media.CustomData.Package) {
                    $expandWindowsImageParams['Package'] = $media.CustomData.Package;
                }

                ExpandWindowsImage @expandWindowsImageParams;
                ## Apply hotfixes (AddDiskImageHotfix)
                AddDiskImageHotfix -Id $Id -Vhd $image -PartitionStyle $partitionStyle;
                ## Configure boot volume (SetDiskImageBootVolume)
                SetDiskImageBootVolume -Vhd $image -PartitionStyle $partitionStyle;
            }
            catch {
                ## Have to ensure VHDX is dismounted before we can delete!
                $imageCreationFailed = $true;
                Write-Error -Message $_;
            }
            finally {
                ## Dismount VHDX
                Dismount-VHD -Path $imagePath;
            }

            if ($imageCreationFailed -eq $true) {
                WriteWarning ($localized.RemovingIncompleteImageWarning -f $imagePath);
                Remove-Item -Path $imagePath -Force;
            }
        } #end if ISO/WIM

        return (Get-Item -Path $imagePath -ErrorAction Ignore);

    } #end process
} #end function New-LabImage