PowerShell advanced function (cmdlet) to create, format, mount and dismount VHD and VHDX files on local and remote computers


I often use Virtual Hard Disk (VHD or VHDX) files for different purposes then disks for virtual machines (VMs).

A few examples:

  • Container for temporary data
  • It is possible to use it in home environment for file backups or for differential backups using Windows Server Backup.
  • I like to use VHD as data disk (D:) on my old Microsoft Surface version 1. I won Surface v1 and I still use it at home. The only problem of my Surface that it has small disk. It is not a good idea to create more partitions because more partitions means more wasted space (every partition must have enough free space). To keep application data and system on different partitions I decided to create VHD and mount it using Scheduled Task when system starts.

The only problem of VHD and PowerShell that it is not possible to use *-VHD cmdlets (New-VHD, Mount-VHD) on a devices without Hyper-V role (for example on my Microsoft Surface). The only possibility is to use PowerShell Storage module (Mount-DiskImage) or Dism module (Mount-WindowsImage) to mount VHD and diskpart to create it.

I decided it to write a new module with following cmdlets (advanced functions with module manifest):

  • New-RvVirtualDisk
  • Mount-RvVirtualDisk
  • Dismount-RvVirtualDisk

Cmdlets (advanced functions) have a lot of possibilities how to use them:

  • It is possible to run it locally or to define ComputerName to run it on remote device (server) or to set Session parameter and use already established session to remote device (server).
  • New-RvVirtualDisk will not only create VHD or VHDX but it can also format it and assign defined drive letter or Mount Point.
  • Mount-RvVirtualDisk will not only mount defined VHD or VHDX but it can also assign access path (drive letter or Mount Point) to existing partition on mounted disk.
  • New-RvVirtualDisk and Mount-RvVirtualDisk return CimInstance (from Get-Volume) that contains DriveLetter property so it possible for example script another operations that automatically copy data to mounted disk.

Examples

Use established session; Create new disk, format it and keep it mounted

$sessionItem = New-PSSession -ComputerName cont2test0.ad1.contoso.com
New-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx' -Mount:$true `
    -Session $sessionItem
Disconnect-PSSession -Session $sessionItem | Remove-PSSession

Invoke commands on multiple servers; Create new disk, format it, specify drive letter (or Mount Point) and file system label and do not keep the disk mounted

New-RvVirtualDisk -Path 'C:\Temp\NewDisk2.vhdx' `
    -SizeBytes 100GB `
    -Dynamic:$true `
    -Format:$true `
    -AccessPath Z: `
    -VolumeLabel 'My new storage' `
    -Mount:$false `
    -ComputerName cont2test0.ad1.contoso.com, cont2test1.ad1.contoso.com

Invoke commands on multiple servers; Mount disk and if partition does not have access path (drive letter or Mount Point) then assign available drive letter

Mount-RvVirtualDisk `
    -Path 'C:\Temp\NewDisk.vhdx' `
    -AddAccessPath:$true `
    -ComputerName cont2test0.ad1.contoso.com, cont2test1.ad1.contoso.com

Mount and add or change drive letter

Mount-RvVirtualDisk `
    -Path 'C:\Temp\NewDisk.vhdx' `
    -AddAccessPath:$true `
    -AccessPath Z:

Dismount piped VHDX files

Get-DiskImage -ImagePath 'C:\Temp\NewDisk.vhdx' |
    Dismount-RvVirtualDisk

New-RvVirtualDisk

Function New-RvVirtualDisk
{
    <#
    .SYNOPSIS
        Create new VHD or VHDX file (virtual disk).

    .DESCRIPTION
        Developer
            Developer: Rudolf Vesely, http://rudolfvesely.com/
            Copyright (c) Rudolf Vesely. All rights reserved
            License: Free for private use only

            "RV" are initials of the developer's name Rudolf Vesely and distingue names of Rudolf Vesely's cmdlets from the other cmdlets.

        Description
            Create new VHD or VHDX file (virtual disk).

            Cmdlet is functional on devices where it is not possible to use *-VHD cmdlets because the device does not have installed Hyper-V role.

        Requirements
            Developed and tested using PowerShell 4.0.

    .PARAMETER Path
        Path to the VHD or VHDX file.

    .PARAMETER SizeBytes
        Size in Bytes.

    .PARAMETER Dynamic
        Possibilities
            $true (default)
                Dynamically expanding virtual disk
            $false
                Virtual disk with fixed size.

    .PARAMETER Format
        Initialize disk (GPT), create partition, volume and format (NTFS).

    .PARAMETER AccessPath
        If specified and volume is formatted then partition will get specified access path.

        Possibilities
            Drive letter (only): X
            Drive letter: X:
            Mount Point: "D:\My disk"

        If not specified and volume is formatted then available drive letter will be assigned to the new partition.

    .PARAMETER VolumeLabel
        Defined file system label for the new volume.

    .PARAMETER Mount
        If $false then the new disk will not be mounted or will be dismounted after formatting.

    .PARAMETER Force
        Remove already existing file and create new.

    .EXAMPLE
        EXAMPLE: Local: Create new disk, format it and keep it mounted'
        New-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx' `
            -SizeBytes 100GB `
            -Dynamic:$true `
            -Format:$true `
            -Mount:$true

    .EXAMPLE
        'Local: Create new disk, format it, specify drive letter (or Mount Point) and volume label and keep it mounted'
        New-RvVirtualDisk -Path 'C:\Temp\NewDisk2.vhdx' `
            -SizeBytes 100GB `
            -Dynamic:$true `
            -Format:$true `
            -AccessPath Z: `
            -VolumeLabel 'My new storage' `
            -Mount:$true `
            -ComputerName cont2test0.ad1.contoso.com

    .EXAMPLE
        'Local: Create new disk, overwrite existing file, format it but do not keep it mounted'
        New-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx' `
            -SizeBytes 100GB `
            -Dynamic:$true `
            -Format:$true `
            -Mount:$false `
            -Force:$true

    .EXAMPLE
        'ComputerName: Create new disk, format it and keep it mounted'
        New-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx' -Mount:$true `
            -ComputerName cont2test0.ad1.contoso.com, cont2test0.ad1.contoso.com

    .EXAMPLE
        'Session: Create new disk, format it and keep it mounted'
        $sessionItem = New-PSSession -ComputerName cont2test0.ad1.contoso.com
        New-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx' -Mount:$true `
            -Session $sessionItem
        Disconnect-PSSession -Session $sessionItem | Remove-PSSession

    .INPUTS

    .OUTPUTS
        Output from Get-Volume:
            Microsoft.Management.Infrastructure.CimInstance

    .LINK
        https://techstronghold.com/
    #>

    [CmdletBinding(
        DefaultParametersetName = 'Path',
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        HelpURI = 'https://techstronghold.com/',
        ConfirmImpact = 'Medium'
    )]

    Param
    (
        [Parameter(
            Mandatory = $false,
            Position = 0,
            ParameterSetName = 'Path',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateLength(1, 255)]
        [Alias('FullName')]
        [string[]]$Path,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [int64]$SizeBytes = 127GB,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$Dynamic = $true,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$Format = $true,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [string]$AccessPath,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [string]$VolumeLabel = 'Data',

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$Mount = $true,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$Force,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [ValidateLength(1, 255)]
        [string[]]$ComputerName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [System.Management.Automation.Runspaces.PSSession[]]$Session
    )

    Begin
    {
        # Configurations
        $ErrorActionPreference = 'Stop'
        if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
        Set-PSDebug -Strict
        Set-StrictMode -Version Latest

        #region Functions
        Function New-RvVirtualDiskProcess
        {
            [CmdletBinding(
                DefaultParametersetName = 'Disk',
                SupportsShouldProcess = $true,
                PositionalBinding = $true,
                HelpURI = 'https://techstronghold.com/',
                ConfirmImpact = 'Medium'
            )]

            Param
            (
                [Parameter(
                    Mandatory = $true,
                    Position = 0,
                    ParameterSetName = 'Disk'
                )]
                [string[]]$Path,

                [Parameter(
                    Mandatory = $true,
                    Position = 1
                    # ParameterSetName = ''
                )]
                [int64]$SizeBytes,

                [Parameter(
                    Mandatory = $true,
                    Position = 2
                    # ParameterSetName = ''
                )]
                [bool]$Dynamic,

                [Parameter(
                    Mandatory = $true,
                    Position = 3
                    # ParameterSetName = ''
                )]
                [bool]$Format,

                [Parameter(
                    Mandatory = $true,
                    Position = 4
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [AllowEmptyString()]
                [string]$AccessPath,

                [Parameter(
                    Mandatory = $true,
                    Position = 5
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [AllowEmptyString()]
                [string]$VolumeLabel,

                [Parameter(
                    Mandatory = $true,
                    Position = 6
                    # ParameterSetName = ''
                )]
                [bool]$Mount,

                [Parameter(
                    Mandatory = $true,
                    Position = 7
                    # ParameterSetName = ''
                )]
                [bool]$Force
            )

            Begin
            {
                # Configurations
                $ErrorActionPreference = 'Stop'
                if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
                Set-PSDebug -Strict
                Set-StrictMode -Version Latest
            }

            Process
            {
                foreach ($pathItem in $Path)
                {
                    <#
                    Create
                    #>

                    # Check if already exists
                    if (Test-Path -Path $pathItem -PathType Leaf)
                    {
                        if ($Force)
                        {
                            Write-Warning -Message ('Item will be removed: {0}' -f $pathItem)
                            Remove-Item -Path $pathItem
                        }
                        else
                        {
                            Write-Error -Message ('Item already exists: {0}' -f $pathItem)
                        }
                    }

                    # Create parent directory
                    elseif (!(Test-Path -Path (Split-Path -Path $pathItem -Parent) -PathType Container))
                    {
                        New-Item -Path (Split-Path -Path $pathItem -Parent) -ItemType directory | Out-Null
                    }

                    # diskpart: Command
                    #     create vdisk file= {[type=<fixed|expandable>] | [parent=] | [source=]} [maximum=] [sd=] [noerr]
                    $diskpartCommand = 'create vdisk file="{0}"' -f $pathItem
                    if ($Dynamic)
                    {
                        $diskpartCommand += ' type=expandable maximum={0}' -f [int64]($SizeBytes / 1MB)
                    }
                    else
                    {
                        $diskpartCommand += ' type=fixed'
                    }

                    # diskpart: Run
                    Write-Debug -Message ('diskpart command: {0}' -f $diskpartCommand)

                    $diskpartOutput = $diskpartCommand | diskpart

                    if ($diskpartOutput -match 'DiskPart successfully')
                    {
                        Write-Debug -Message 'OK'
                    }
                    else
                    {
                        Write-Error -Message ('Error during trial to create file: {0}' -f $pathItem)
                    }



                    <#
                    Mount and format
                    #>

                    if ($Format -or $Mount)
                    {
                        $vhdItem = Mount-DiskImage -ImagePath $pathItem -PassThru

                        if ($Format)
                        {
                            $vhdItem | Get-DiskImage |
                                Get-Disk |
                                Initialize-Disk -PartitionStyle GPT -PassThru |
                                New-Partition -UseMaximumSize -OutVariable partitionItem |
                                Format-Volume -FileSystem NTFS -NewFileSystemLabel $VolumeLabel -Confirm:$false |
                                Out-Null

                            if ($AccessPath)
                            {
                                # Drive letter only (only single character)
                                if ($AccessPath -match '^[a-zA-Z]$')
                                {
                                    $accessPathItem = '{0}:' -f $AccessPath.ToUpper()
                                }

                                # Mount Point
                                elseif ($AccessPath -match '^[a-zA-Z]:\\\w')
                                {
                                    if (!(Test-Path -Path $AccessPath -PathType Container))
                                    {
                                        New-Item -Path (Split-Path -Path $pathItem -Parent) `
                                            -ItemType directory | Out-Null
                                    }
                                    $accessPathItem = $AccessPath
                                }

                                # Other, for example X:
                                else
                                {
                                    $accessPathItem = $AccessPath
                                }

                                Write-Verbose -Message ('Add access path: {0}' -f $accessPathItem)

                                $partitionItem | Add-PartitionAccessPath -AccessPath $accessPathItem
                            }
                            else
                            {
                                Write-Verbose -Message 'Add available drive letter'

                                $partitionItem | Add-PartitionAccessPath -AssignDriveLetter
                            }

                            # Return
                            $partitionItem | Get-Volume
                        }
                        else
                        {
                            # Return
                            $vhdItem | Get-DiskImage |
                                Get-Disk
                        }

                        if (!$Mount)
                        {
                            $vhdItem | Dismount-DiskImage
                        }
                    }
                }
            }

            End
            {
            }
        }
        #endregion
    }

    Process
    {
        # Remote device: Session
        if ($Session)
        {
            foreach ($sessionItem in $Session)
            {
                Invoke-Command `
                    -Session $sessionItem `
                    -ArgumentList $Path, $SizeBytes, $Dynamic, $Format, $AccessPath, $VolumeLabel, $Mount, $Force `
                    -ScriptBlock ${Function:New-RvVirtualDiskProcess}
            }
        }
        else
        {
            foreach ($computerNameItem in $(if ($ComputerName) { $ComputerName } else { '.' } ))
            {
                # Local device
                if ($computerNameItem -eq '.' -or $computerNameItem -eq $env:COMPUTERNAME)
                {
                    New-RvVirtualDiskProcess `
                        -Path $Path `
                        -SizeBytes $SizeBytes `
                        -Dynamic:$Dynamic `
                        -Format:$Format `
                        -AccessPath $AccessPath `
                        -VolumeLabel $VolumeLabel `
                        -Mount:$Mount `
                        -Force:$Force
                }

                # Remote device: ComputerName
                else # if ($ComputerName)
                {
                    Invoke-Command `
                        -ComputerName $computerNameItem `
                        -ArgumentList $Path, $SizeBytes, $Dynamic, $Format, $AccessPath, $VolumeLabel, $Mount, $Force `
                        -ScriptBlock ${Function:New-RvVirtualDiskProcess}
                }
            }
        }
    }

    End
    {
    }
}

Mount-RvVirtualDisk

Function Mount-RvVirtualDisk
{
    <#
    .SYNOPSIS
        Mount VHD or VHDX file (virtual disk).

    .DESCRIPTION
        Developer
            Developer: Rudolf Vesely, http://rudolfvesely.com/
            Copyright (c) Rudolf Vesely. All rights reserved
            License: Free for private use only

            "RV" are initials of the developer's name Rudolf Vesely and distingue names of Rudolf Vesely's cmdlets from the other cmdlets.

        Description
            Mount VHD or VHDX file (virtual disk).

            Cmdlet is functional on devices where it is not possible to use *-VHD cmdlets because the device does not have installed Hyper-V role.

        Requirements
            Developed and tested using PowerShell 4.0.

    .PARAMETER Path
        Path to the VHD or VHDX file.

    .PARAMETER DiskImage
        Output from Get-DiskImage (Microsoft.Management.Infrastructure.CimInstance).

    .PARAMETER AddAccessPath
        If first partition on the mounted disk does not have access path (drive letter or Mount Point) then add it.

        if -AccessPath parameter is not specified and the first partition does not have existing access path then add available drive letter.

    .PARAMETER AccessPath
        If specified then first partition will get specified access path.

        Possibilities
            Drive letter (only): X
            Drive letter: X:
            Mount Point: "D:\My disk"

    .EXAMPLE
        'Local: Mount and do not try to make the partition accessible'
        Mount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -AddAccessPath:$false

    .EXAMPLE
        'Local: Mount and if partition does not have access path (drive letter or Mount Point) then assign available drive letter'
        Mount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -AddAccessPath:$true

    .EXAMPLE
        'Local: Mount and if partition does not have defined access path then assign defined drive letter or Mount Point'
        Mount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -AddAccessPath:$true `
            -AccessPath Z:

    .EXAMPLE
        'ComputerName: Mount and do not try to make the partition accessible'
        Mount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -AddAccessPath:$false `
            -ComputerName cont2test0.ad1.contoso.com, cont2test0.ad1.contoso.com

    .EXAMPLE
        'Session: Mount and do not try to make the partition accessible'
        $sessionItem = New-PSSession -ComputerName cont2test0.ad1.contoso.com
        Mount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -AddAccessPath:$false `
            -Session $sessionItem
        Disconnect-PSSession -Session $sessionItem | Remove-PSSession

    .INPUTS
        Output from Get-DiskImage:
            Microsoft.Management.Infrastructure.CimInstance

    .OUTPUTS
        Output from Get-Volume:
            Microsoft.Management.Infrastructure.CimInstance

    .LINK
        https://techstronghold.com/
    #>

    [CmdletBinding(
        DefaultParametersetName = 'DiskImage',
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        HelpURI = 'https://techstronghold.com/',
        ConfirmImpact = 'Medium'
    )]

    Param
    (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = 'Path',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateLength(1, 255)]
        [Alias('FullName')]
        [string[]]$Path,

        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = 'DiskImage',
            ValueFromPipeline = $true
        )]
        [Microsoft.Management.Infrastructure.CimInstance[]]$DiskImage,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$AddAccessPath = $true,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [string]$AccessPath,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [ValidateLength(1, 255)]
        [string[]]$ComputerName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [System.Management.Automation.Runspaces.PSSession[]]$Session
    )

    Begin
    {
        # Configurations
        $ErrorActionPreference = 'Stop'
        if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
        Set-PSDebug -Strict
        Set-StrictMode -Version Latest

        #region Functions
        Function Mount-RvVirtualDiskProcess
        {
            [CmdletBinding(
                DefaultParametersetName = 'Disk',
                SupportsShouldProcess = $true,
                PositionalBinding = $true,
                HelpURI = 'https://techstronghold.com/',
                ConfirmImpact = 'Medium'
            )]

            Param
            (
                [Parameter(
                    Mandatory = $true,
                    Position = 0,
                    ParameterSetName = 'Disk'
                )]
                [AllowNull()]
                [string[]]$Path,

                [Parameter(
                    Mandatory = $true,
                    Position = 1
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [Microsoft.Management.Infrastructure.CimInstance[]]$DiskImage,

                [Parameter(
                    Mandatory = $true,
                    Position = 2
                    # ParameterSetName = ''
                )]
                [bool]$AddAccessPath,

                [Parameter(
                    Mandatory = $true,
                    Position = 3
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [AllowEmptyString()]
                [string]$AccessPath
            )

            Begin
            {
                # Configurations
                $ErrorActionPreference = 'Stop'
                if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
                Set-PSDebug -Strict
                Set-StrictMode -Version Latest
            }

            Process
            {
                <#
                Mount
                #>

                $diskImageItems = @()

                foreach ($pathItem in $Path)
                {
                    $diskImageItems += Mount-DiskImage -ImagePath $pathItem -PassThru | Get-DiskImage
                }
                foreach ($diskImageItem in $DiskImage)
                {
                    $diskImageItems += Mount-DiskImage -ImagePath $diskImageItem.ImagePath -PassThru | Get-DiskImage
                }



                <#
                Access paths
                #>

                foreach ($diskImageItem in $diskImageItems)
                {
                    # Correct defind access path
                    $mountPoint = $false
                    if ($AccessPath)
                    {
                        # Drive letter only (only single character)
                        if ($AccessPath -match '^[a-zA-Z]$')
                        {
                            $accessPathItem = '{0}:' -f $AccessPath.ToUpper()
                        }

                        # Mount Point
                        elseif ($AccessPath -match '^[a-zA-Z]:\\\w')
                        {
                            $accessPathItem = $AccessPath
                            $mountPoint = $true
                        }

                        # Other, for example X:
                        else
                        {
                            $accessPathItem = $AccessPath
                        }

                        # Remove last backslash for further comparison
                        $accessPathItem -replace '\\$', ''
                    }
                    else
                    {
                        $accessPathItem = $null
                    }

                    # Add access path
                    if ($AddAccessPath)
                    {
                        $partitionItem = $diskImageItem |
                            Get-Disk |
                            Get-Partition |
                            Where-Object -Property Type -EQ Basic |
                            Select-Object -First 1

                        # Get access paths without last backslash and do not include partition GUID
                        $partitionItemAccessPathItems = $partitionItem |
                            Select-Object -ExpandProperty AccessPaths |
                            Where-Object -FilterScript { $_ -notmatch '^\\\\\?' } |
                            ForEach-Object -Process { $_ -replace '\\$', '' }

                        # Add specific access path
                        if ($accessPathItem)
                        {
                            if ($partitionItemAccessPathItems -contains $accessPathItem)
                            {
                                Write-Verbose -Message ('Partition has defined access path; Current access paths: {0}' -f ($partitionItemAccessPathItems -join ', '))
                            }
                            else
                            {
                                if ($mountPoint -and !(Test-Path -Path $accessPathItem -PathType Container))
                                {
                                    New-Item -Path $accessPathItem -ItemType directory | Out-Null
                                }

                                Write-Verbose -Message ('Partition does not have defined access path; Add: {0}' -f $accessPathItem)

                                $partitionItem | Add-PartitionAccessPath -AccessPath $accessPathItem
                            }
                        }

                        # Add any access path
                        else
                        {
                            if ($partitionItemAccessPathItems)
                            {
                                Write-Verbose -Message ('Partition has following access paths: {0}' -f ($partitionItemAccessPathItems -join ', '))
                            }
                            else
                            {
                                Write-Verbose -Message 'No access path; Assign available drive letter'
                                $partitionItem | Add-PartitionAccessPath -AssignDriveLetter
                            }
                        }

                        # Return
                        $partitionItem | Get-Volume
                    }
                    else
                    {
                        # Return
                        $diskImageItem | Get-Disk | Get-Partition | Get-Volume
                    }
                }
            }

            End
            {
                # Refresh Windows PowerShell Drives
                Get-PSDrive | Out-Null
            }
        }
        #endregion
    }

    Process
    {
        # Remote device: Session
        if ($Session)
        {
            foreach ($sessionItem in $Session)
            {
                Invoke-Command `
                    -Session $sessionItem `
                    -ArgumentList $Path, $DiskImage, $AddAccessPath, $AccessPath `
                    -ScriptBlock ${Function:Mount-RvVirtualDiskProcess}
            }
        }
        else
        {
            foreach ($computerNameItem in $(if ($ComputerName) { $ComputerName } else { '.' } ))
            {
                # Local device
                if ($computerNameItem -eq '.' -or $computerNameItem -eq $env:COMPUTERNAME)
                {
                    Mount-RvVirtualDiskProcess `
                        -Path $Path `
                        -DiskImage $DiskImage `
                        -AddAccessPath:$AddAccessPath `
                        -AccessPath $AccessPath
                }

                # Remote device: ComputerName
                else # if ($ComputerName)
                {
                    Invoke-Command `
                        -ComputerName $computerNameItem `
                        -ArgumentList $Path, $DiskImage, $AddAccessPath, $AccessPath `
                        -ScriptBlock ${Function:Mount-RvVirtualDiskProcess}
                }
            }
        }
    }

    End
    {
    }
}

Dismount-RvVirtualDisk

Function Dismount-RvVirtualDisk
{
    <#
    .SYNOPSIS
        Dismount VHD or VHDX file (virtual disk).

    .DESCRIPTION
        Developer
            Developer: Rudolf Vesely, http://rudolfvesely.com/
            Copyright (c) Rudolf Vesely. All rights reserved
            License: Free for private use only

            "RV" are initials of the developer's name Rudolf Vesely and distingue names of Rudolf Vesely's cmdlets from the other cmdlets.

        Description
            Dismount VHD or VHDX file (virtual disk).

            Cmdlet is functional on devices where it is not possible to use *-VHD cmdlets because the device does not have installed Hyper-V role.

        Requirements
            Developed and tested using PowerShell 4.0.

    .PARAMETER Path
        Path to the VHD or VHDX file.

    .PARAMETER DiskImage
        Output from Get-DiskImage (Microsoft.Management.Infrastructure.CimInstance).

    .EXAMPLE
        'Local: Dismount defined VHDX file'
        Dismount-RvVirtualDisk -Path 'C:\Temp\NewDisk.vhdx'

    .EXAMPLE
        'Local: Dismount defined VHDX file; Pipeline'
        Get-ChildItem -Path 'C:\Temp' -Filter *.vhdx |
            Dismount-RvVirtualDisk

    .EXAMPLE
        'Local: Dismount defined VHDX file; Pipeline'
        Get-DiskImage -ImagePath 'C:\Temp\NewDisk.vhdx' |
            Dismount-RvVirtualDisk

    .EXAMPLE
        'ComputerName: Dismount defined VHDX file'
        Dismount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -ComputerName cont2test0.ad1.contoso.com, cont2test0.ad1.contoso.com

    .EXAMPLE
        'Session: Dismount defined VHDX file'
        $sessionItem = New-PSSession -ComputerName cont2test0.ad1.contoso.com
        Dismount-RvVirtualDisk `
            -Path 'C:\Temp\NewDisk.vhdx' `
            -Session $sessionItem
        Disconnect-PSSession -Session $sessionItem | Remove-PSSession

    .INPUTS
        Output from Get-DiskImage:
            Microsoft.Management.Infrastructure.CimInstance

    .OUTPUTS

    .LINK
        https://techstronghold.com/
    #>

    [CmdletBinding(
        DefaultParametersetName = 'DiskImage',
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        HelpURI = 'https://techstronghold.com/',
        ConfirmImpact = 'Medium'
    )]

    Param
    (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = 'Path',
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateLength(1, 255)]
        [Alias('FullName')]
        [string[]]$Path,

        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = 'DiskImage',
            ValueFromPipeline = $true
        )]
        [Microsoft.Management.Infrastructure.CimInstance[]]$DiskImage,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [ValidateLength(1, 255)]
        [string[]]$ComputerName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [System.Management.Automation.Runspaces.PSSession[]]$Session
    )

    Begin
    {
        # Configurations
        $ErrorActionPreference = 'Stop'
        if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
        Set-PSDebug -Strict
        Set-StrictMode -Version Latest
    }

    Process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Path')
        {
            $pathItems = $Path
        }
        else # if ($PSCmdlet.ParameterSetName -eq 'DiskImage')
        {
            $pathItems = $DiskImage.ImagePath
        }

        # Remote device: Session
        if ($Session)
        {
            foreach ($sessionItem in $Session)
            {
                Invoke-Command `
                    -Session $sessionItem `
                    -ScriptBlock { Dismount-DiskImage -ImagePath $Using:pathItems }
            }
        }
        else
        {
            foreach ($computerNameItem in $(if ($ComputerName) { $ComputerName } else { '.' } ))
            {
                # Local device
                if ($computerNameItem -eq '.' -or $computerNameItem -eq $env:COMPUTERNAME)
                {
                    Dismount-RvVirtualDiskProcess `
                        -Path $pathItems
                }

                # Remote device: ComputerName
                else # if ($ComputerName)
                {
                    Invoke-Command `
                        -ComputerName $computerNameItem `
                        -ScriptBlock { Dismount-DiskImage -ImagePath $Using:pathItems }
                }
            }
        }
    }

    End
    {
    }
}

One response to “PowerShell advanced function (cmdlet) to create, format, mount and dismount VHD and VHDX files on local and remote computers”

  1. Function New-RvVirtualDisk :
    $diskpartCommand += ‘ type=fixed’
    should be :
    $diskpartCommand += ‘ type=fixed’ maximum={0}’ -f [int64]($SizeBytes / 1MB)
    ?

Leave a Reply

Your email address will not be published. Required fields are marked *

Active Directory Advanced function AlwaysOn Availability Groups AlwaysOn Failover Cluster Instances Building Cloud Cloud Cluster Cmdlet Database Deployment Design DFS Domain Controller DSC Fabric Failover Clustering File Server Group Policy Hardware Profile Host Hyper-V Installation Library Library Asset Library Server Network Operations Manager Orchestrator PowerShell PowerShell User Group PowerShell Workflow Security Service Manager SQL Server Storage System Center Template Time Time Synchronization Tips Virtual Machine Virtual Machine Manager VM Network VM Template Windows Server 2012 R2