Share via


how to mount a System partition

As I've mentioned before, the on the machines with EFI BIOS the boot files are located on a separate System partition that can be mounted. However the direct mounting of it doesn't work so well, leaving a drive letter busy even after unmounting. If you want to modify a lot of VHDs for the v2 VMs with the system partitions, this becomes a problem. The solution is to temporarily change the partition type to Basic, mount it, do the work, unmount, and change the type back. Here is a code example for that.

P.S. In retrospect, the comment about adding "Sleep 1" seems to be not quite correct. The problem seems to be not with the mounting of the partition as such but with PowerShell seeing it. For some reason PowerShell has a delay there, and there seems to be no reliable way to work around it by adding delays, it still fails once in a while. The fix is to run "Get-PSDrive" that will re-scan the list of the drives, run it like: $null = Get-PSDrive.

 function Mount-SystemPartition
{
<#
.SYNOPSIS
Mount the system (EFI) partition of a VHDX image.
Don't forget to call "DisMount-SystemPartition -ImagePath $ImagePath -DriveLetter $letter" when done
(unless this function throws, then there is no need to dismount).
May also need to call "Sleep 1" before accessing the partition, for some reason
it's not always immediate.

For a workaround of the issues with the direct mounting of the system partition,
the partition type gets changed to basic for the duration of the mount.
The correct dismounting as described above changes it back.

.OUTPUT
The disk letter of the mounted partition. If the VHDX has no system partition, returns
nothing but the image is still left mounted and has to be unmounted afterwards as usual.

.EXAMPLE
$dl = Mount-SystemPartition -ImagePath $ImagePath
if ($dl) {
    dir "${dl}:\"
}
DisMount-SystemPartition -ImagePath $ImagePath -DriveLetter $dl

#>
    [CmdletBinding()]
    param(
        ## Path of the VHDX image. A VHD image can be used too but typically the EFI
        ## setup is used with VHDX.
        [Parameter(Mandatory=$true)]
        [string] $ImagePath
    )

    # GUIDs for GPT partition types
    $SystemTypeGuid = "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"
    $BasicTypeGuid = "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}"

    Mount-DiskImage -ImagePath $ImagePath -Verbose:($VerbosePreference -ne "SilentlyContinue")
    # The object returned by "Mount-DiskImage" doesn't contain all the
    # information, so do the Get-DiskImage afresh.
    $syspart = @(Get-DiskImage -ImagePath $ImagePath | Get-Disk | Get-Partition | ? { $_.Type -eq "System" } )
    if (!$syspart) {
        Write-Verbose "No system partition found on $ImagePath"
        return # no system partition
    }

    try {
        $syspart | Set-Partition -GptType $BasicTypeGuid -Verbose:($VerbosePreference -ne "SilentlyContinue")

        # with the changed type
        $part = Get-DiskImage -ImagePath $ImagePath | Get-Disk | Get-Partition -PartitionNumber $syspart.PartitionNumber
        if ($part.DriveLetter) {
            # already mounted, just use it (this might happen only if there was a girevous failure during
            # the previous mount, when the disk got dismounted and partition type restored without
            # unassgining the drive letter)
            Write-Verbose "System partition of $ImagePath is already mounted on $($part.DriveLetter):"
            return $part.DriveLetter
        }

        $part | Add-PartitionAccessPath -AssignDriveLetter -EA Stop -Verbose:($VerbosePreference -ne "SilentlyContinue")

        $part = Get-DiskImage -ImagePath $ImagePath | Get-Disk | Get-Partition -PartitionNumber $syspart.PartitionNumber
        Write-Verbose "System partition of $ImagePath has been mounted on $($part.DriveLetter):"
        return $part.DriveLetter
    } catch {
        $error = "$($_.Exception.Message)`r`n"
        Write-Warning "$($_.Exception.Message)`r`nFailed to mount the virtual disk $ImagePath, cleaning up"

        try {
            $part = Get-DiskImage -ImagePath $ImagePath | Get-Disk | Get-Partition -PartitionNumber $syspart.PartitionNumber
            if ($part.DriveLetter) {
                $part | Remove-PartitionAccessPath -AccessPath ($part.DriveLetter + ":") -EA Stop -Verbose:($VerbosePreference -ne "SilentlyContinue")
            }
        } catch {
            $error += "$($_.Exception.Message)`r`n"
            Write-Warning "$($_.Exception.Message)`r`nFailed to dismount the drive letter $DriveLetter, continuing with an attempt to dismount the virtual disk $ImagePath"
        }
        try {
            $syspart | Set-Partition -GptType $SystemTypeGuid -Verbose:($VerbosePreference -ne "SilentlyContinue")
        } catch {
            $error += "$($_.Exception.Message)`r`n"
            Write-Warning "$($_.Exception.Message)`r`nFailed to restore the system type of the partition $($syspart.PartitionNumber), continuing with an attempt to dismount the virtual disk $ImagePath"
        }
        try {
            DisMount-DiskImage -ImagePath $ImagePath -Verbose:($VerbosePreference -ne "SilentlyContinue")
        } catch {
            $error += "$($_.Exception.Message)`r`n"
            Write-Warning "$($_.Exception.Message)`r`nFailed to dismount the virtual disk $ImagePath"
        }

        throw $error
    }
}

function DisMount-SystemPartition
{
<#
.SYNOPSIS
Dismount the system (EFI) partition of a VHDX image and the image itself.
#>
    [CmdletBinding()]
    param(
        ## Path of the VHDX image. A VHD image can be used too but typically the EFI
        ## setup is used with VHDX.
        [Parameter(Mandatory=$true)]
        [string] $ImagePath,
        ## The drive letter where the partition is mounted. May be empty - in this case
        ## the partition itself is expected to not be mounted due to some error but
        ## this function will still unload the VHDX image.
        [string] $DriveLetter
    )

    # GUIDs for GPT partition types
    $SystemTypeGuid = "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"
    $BasicTypeGuid = "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}"

    # the collected error
    $error = $null

    try {
        if ($DriveLetter) {
            $syspart = Get-Partition -DriveLetter $DriveLetter
            try {
                $syspart | Remove-PartitionAccessPath -AccessPath "${DriveLetter}:" -EA Stop -Verbose:($VerbosePreference -ne "SilentlyContinue")
            } catch {
                $error += "$($_.Exception.Message)`r`n"
                Write-Warning "$($_.Exception.Message)`r`nFailed to dismount the drive letter $DriveLetter, continuing with an attempt to dismount the virtual disk $ImagePath"
            }
            try {
                $syspart | Set-Partition -GptType $SystemTypeGuid -EA Stop -Verbose:($VerbosePreference -ne "SilentlyContinue")
            } catch {
                $error += "$($_.Exception.Message)`r`n"
                Write-Warning "$($_.Exception.Message)`r`nFailed to restore the system type of the partition $($syspart.PartitionNumber), continuing with an attempt to dismount the virtual disk $ImagePath"
            }
        }

        DisMount-DiskImage -ImagePath $ImagePath -Verbose:($VerbosePreference -ne "SilentlyContinue")
    } catch {
        $error += "$($_.Exception.Message)`r`n"
    }

    if ($error) {
        throw $error
    }
}