functions/New-Vhdx.ps1

function New-Vhdx {
<#
    .SYNOPSIS
        Creates a new vdhx file.
     
    .DESCRIPTION
        Creates a new vdhx file.
        It comes preconfigured with a single volume and can have files and folders pre-assigned to it.
     
    .PARAMETER Path
        Path to the VHDX file to create.
        Folder must exist, file should not.
     
    .PARAMETER Type
        How should the disk be provisioned.
        - Dynamic: File grows with its content (less consumption)
        - Fixed: File size equals size limit (slightly better performance)
        Defaults to: Dynamic
     
    .PARAMETER Size
        How large should the disk be?
        Defaults to 5GB
        Note: With "dynamic" disk type, the file backing it grows as its content is added, so this size limit need not be the actual storage consumed.
     
    .PARAMETER Label
        What label to add to the volume created in this disk.
        Defaults to: "Data"
     
    .PARAMETER Content
        Any files or folders to add to the disk.
     
    .EXAMPLE
        PS C:\> Get-ChildItem C:\install\sccm\ | New-Vhdx -Path 'C:\disks\sccm-content.vhdx'
     
        Creates a new vhdx file as 'C:\disks\sccm-content.vhdx', adding all files and folders from 'C:\install\sccm\' to it.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [PsfValidateScript('PSFramework.Validate.FSPath.FileOrParent', ErrorString = 'PSFramework.Validate.FSPath.FileOrParent')]
        [string]
        $Path,

        [ValidateSet('Dynamic', 'Fixed')]
        [string]
        $Type = 'Dynamic',

        [PSFSize]
        $Size = 5GB,

        [string]
        $Label = 'Data',

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string[]]
        $Content
    )
    
    begin {
        Assert-Elevation -Cmdlet $PSCmdlet
        
        $typeMapping = @{
            Dynamic = 'expandable'
            Fixed   = 'fixed'
        }

        $resolvedPath = Resolve-PSFPath -Path $Path -Provider FileSystem -SingleItem -NewChild
        # Normalize path to avoid legacy-shortening of folder names breaking later comparisons
        $parent = Split-Path -Path $resolvedPath
        $fileName = Split-Path -Path $resolvedPath -Leaf
        $resolvedPath = Join-Path -Path (Get-Item -Path $parent).FullName -ChildPath $fileName

        $diskPartCommand = 'create vdisk file="{0}" maximum={1} type={2}' -f $resolvedPath, $Size.Megabyte, $typeMapping[$Type]
        $result = Invoke-Diskpart -ArgumentList $diskPartCommand
        
        if (-not $result.Success) {
            Stop-PSFFunction -String 'New-Vhdx.CreateDisk.Failed' -StringValues $resolvedPath, $result.Errors -EnableException $true
        }
        

        $result = Invoke-Diskpart -ArgumentList @(
            ('select vdisk file="{0}"' -f $resolvedPath)
            'attach vdisk'
            'create partition primary'
            ('format fs=NTFS label="{0}" quick' -f $Label)
            'assign'
        )
        if (-not $result.Success) {
            Stop-PSFFunction -String 'New-Vhdx.PrepareDisk.Failed' -StringValues $resolvedPath, $result.Errors -EnableException $true
        }

        $start = Get-Date
        do {
            Start-Sleep -Milliseconds 200
            $disk = Get-Disk | Where-Object Location -EQ $resolvedPath
            $volume = $disk | Get-Partition | Get-Volume
            if ($start.AddMinutes(1) -lt (Get-Date)) {
                Write-PSFMessage -Level Warning -String 'New-Vhdx.Volume.Timeout'
                break
            }
            $rootPath = '{0}:\' -f $volume.DriveLetter
            $null = Get-PSProvider | Write-Output
            if ($volume.DriveLetter -and -not (Test-Path -Path $rootPath)) {
                $null = New-PSDrive -Name $volume.DriveLetter -PSProvider FileSystem -Root $rootPath -ErrorAction Ignore
            }
        }
        until ($volume.DriveLetter -and (Test-Path -Path $rootPath))
    }
    process {
        foreach ($inputItem in $Content) {
            Write-PSFMessage -String 'New-Vhdx.Copying' -StringValues $inputItem, $rootPath
            Invoke-PSFProtectedCommand -ActionString 'New-Vhdx.Copying' -ActionStringValues $inputItem, $rootPath -Target $volume.DriveLetter -ScriptBlock {
                Copy-Item -Path $inputItem -Destination $rootPath -Force -Recurse -ErrorAction Stop
            } -PSCmdlet $PSCmdlet -Continue
        }
    }
    end {
        Dismount-Vhdx -Path $resolvedPath -EnableException
    }
}