# encoding: ascii # api: powershell # title: VHDFunctions.psm1 # description: Here’s several functions for working with VHD’s in Windows 7. I’ve been working with PowerShell for about a year and this is my first go at a module. I’m a sysadmin and not a developer so some of my solutions are in that mode of thinking. There’s probably .NET ways to accomplish what I did and I’m certainly open to learning if there’s a better way. I’ve found these functions useful and hopefully someone else out there will too. Enjoy. # version: 6.1 # type: function # author: Rich Kusak # license: CC0 # function: Dismount-VHD # x-poshcode-id: 1449 # x-derived-from-id: 1451 # x-archived: 2011-05-23T01:07:05 # x-published: 2011-11-02T14:26:00 # # <# Name: VHDFunctions.psm1 Author: Rich Kusak (rkusak@cbcag.edu) Created: 2009-10-23 LastEdit: 2009-11-02 15:54 Included Functions: Dismount-VHD Initialize-VHD Mount-VHD New-VHD Set-VHDBootConfiguration Test-VHD #> <# .SYNOPSIS Dismount a VHD file from the system. .DESCRIPTION This function wraps the consistancy of PowerShell around the Diskpart utility. A Diskpart script is created to automate the dismount (detach) of a VHD file from the system. Optionally, the VHD file can be deleted following detachment. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER Remove Removes (deletes) the VHD file after dismounting it. .PARAMETER NoConfirm Supresses the delete confirmation prompt. .PARAMETER DiskpartScript Specifies the path location of the Diskpart script file. Default location is $env:SystemDrive This file is deleted at the conclusion of the script. .PARAMETER Rescan Instructs Diskpart to rescan the system for available storage resources. .EXAMPLE Dismount-VHD -Path C:\test.vhd Dismounts the specified VHD file. .EXAMPLE Dismount-VHD -Path C:\test.vhd -Remove Dismounts the specified VHD file and then deletes it. .NOTES Name: Dismount-VHD.ps1 Author: Rich Kusak Created: 2009-10-22 LastEdit: 2009-10-26 11:35 #Requires -Version 2.0 #> function Dismount-VHD { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] [string]$Path, [switch]$Remove, [switch]$NoConfirm, [string]$DiskpartScript = "$env:SystemDrive\DiskpartScript.txt", [switch]$Rescan ) begin { function InvokeDiskpart { Diskpart.exe /s $DiskpartScript } function RemoveVHD { switch ($NoConfirm) { $false { ## Prompt for confirmation to delete the VHD file ## "" ; Write-Warning "Are you sure you want to delete the file ""$Path""?" $Prompt = Read-Host "Type ""YES"" to continue or anything else to break" if ($Prompt -ceq 'YES') { Remove-Item -Path $Path -Force "" ; Write-Host "VHD ""$Path"" deleted!" ; "" } else { "" ; Write-Host "Script terminated without deleting the VHD file." ; "" } } $true { ## Confirmation prompt suppressed ## Remove-Item -Path $Path -Force "" ; Write-Host "VHD ""$Path"" deleted!" ; "" } } } ## Validate Operating System Version ## if (Get-WmiObject win32_OperatingSystem -Filter "Version < '6.1'") {throw "The script operation requires at least Windows 7."} } process{ ## DiskPart Script Content ## Here-String statement purposefully not indented ## @" $(if ($Rescan) {'Rescan'}) Select VDisk File="$Path"`nDetach VDisk Exit "@ | Out-File -FilePath $DiskpartScript -Encoding ASCII -Force InvokeDiskpart } end { if ($Remove) {RemoveVHD} Remove-Item -Path $DiskpartScript -Force ; "" } } Export-ModuleMember -Function Dismount-VHD <# .SYNOPSIS Initialize a VHD by preparing it for use. .DESCRIPTION This function wraps the consistancy of PowerShell around the Diskpart utility. A Diskpart script is created to automate initializing a VHD. The script creates a partition, assigns a drive letter, and formats a mounted VHD. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER Drive A drive letter to assign to the mounted VHD. If not specified the system will auto assign the next available drive letter. .PARAMETER Label A volume label to assign to the mounted VHD. .PARAMETER DiskpartScript Specifies the path location of the Diskpart script file. Default location is $env:SystemDrive This file is deleted at the conclusion of the script. .PARAMETER Rescan Instructs Diskpart to rescan the system for available storage resources. .EXAMPLE Initialize-VHD C:\test.vhd X: TestVHD Initializes the VHD at path C:\test.vhd assign it to drive letter X: and give it the volume label "TestVHD". .NOTES Name: Initialize-VHD Author: Rich Kusak Created: 2009-10-22 LastEdit: 2009-10-26 15:11 #Requires -Version 2.0 #> function Initialize-VHD { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] [string]$Path, [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$false)] [string]$Drive, [Parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false)] [string]$Label, [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$false)] [string]$DiskpartScript = "$env:SystemDrive\DiskpartScript.txt", [switch]$NoConfirm, [switch]$Rescan ) begin { function InvokeDiskpart { Diskpart.exe /s $DiskpartScript } ## Validate Operating System Version ## if (Get-WmiObject win32_OperatingSystem -Filter "Version < '6.1'") {throw "The script operation requires at least Windows 7."} ## Validate -Drive parameter ## if ($Drive) { $Reserved = @('A:','B:','C:') $Reserved += (Get-WmiObject win32_LogicalDisk -Property DeviceID | ForEach-Object {$_.DeviceID}) switch ($Drive) { {($_ -notmatch "^[a-z]$") -and ($_ -notmatch "^[a-z]:$")} {throw "The drive letter ""$_"" is invalid."} {$_ -notmatch ":"} {$Drive += ":"} {$Reserved -contains $Drive} {throw "The drive letter ""$_"" is reserved."} } } if (!$NoConfirm) { "" ; Write-Warning "The VHD ""$Path"" is about to initialized. Any existing data will be destroyed!`nAre you sure you want to continue?" ; "" $Prompt = Read-Host "Type ""YES"" to continue or anything else to break" if ($Prompt -cne 'YES') {Write-Host "Function terminated by user."; "" ; break} } } process { ## Diskpart Script Content ## Here-String statement purposefully not indented ## @" $(if ($Rescan) {'Rescan'}) Select VDisk File="$Path" Clean Create Partition Primary Format Quick FS=NTFS $(if ($Label) {"Label=""$Label"""}) $(if ($Drive) {"Assign Letter=$Drive"} else {'Assign'}) Detail VDisk Exit "@ | Out-File -FilePath $DiskpartScript -Encoding ASCII -Force } end { InvokeDiskpart Remove-Item -Path $DiskpartScript -Force ; "" Write-Host "The VHD ""$Path"" has been successfully initialized." ; "" } } Export-ModuleMember -Function Initialize-VHD <# .SYNOPSIS Mount a VHD to the system. .DESCRIPTION This function wraps the consistancy of PowerShell around the Diskpart utility. A Diskpart script is created to automate mounting (attach) a VHD file to the system. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER DiskpartScript Specifies the path location of the Diskpart script file. Default location is $env:SystemDrive This file is deleted at the conclusion of the script. .PARAMETER Rescan Instructs Diskpart to rescan the system for available storage resources. .NOTES Name: Mount-VHD.ps1 Author: Rich Kusak Created: 2009-10-22 LastEdit: 2009-10-26 09:25 #Requires -Version 2.0 #> function Mount-VHD { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] [string]$Path, [string]$DiskpartScript = "$env:SystemDrive\DiskpartScript.txt", [switch]$Rescan ) begin { function InvokeDiskpart { Diskpart.exe /s $DiskpartScript } ## Validate Operating System Version ## if (Get-WmiObject win32_OperatingSystem -Filter "Version < '6.1'") {throw "The script operation requires at least Windows 7."} } process{ ## Diskpart Script Content ## Here-String statement purposefully not indented ## @" $(if ($Rescan) {'Rescan'}) Select VDisk File="$Path"`nAttach VDisk Exit "@ | Out-File -FilePath $DiskpartScript -Encoding ASCII -Force InvokeDiskpart } end { Remove-Item -Path $DiskpartScript -Force ; "" Write-Host "The VHD ""$Path"" has been successfully mounted." ; "" } } Export-ModuleMember -Function Mount-VHD <# .SYNOPSIS Create a new VHD file. .DESCRIPTION This function wraps the consistancy of PowerShell around the Diskpart utility. A Diskpart script is created to automate the creation of the VHD. Optionally, the VHD can be mounted immediately following the creation process. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER Maximum The maximum space allocated for the VHD to use. .PARAMETER Fixed Creates a fixed disk VHD file. By default a dynamically expanding VHD file is created. .PARAMETER Mount Mount (attach) the VHD to the system making it available to Windows. .PARAMETER Prepare Prepares the VHD for use by partitioning, Mount (attach) the VHD to the system making it available to Windows. .PARAMETER NoConfirm Supresses the maximum validation warning confirmation prompt. .PARAMETER DiskpartScript Specifies the path location of the Diskpart script file. Default location is $env:SystemDrive This file is deleted at the conclusion of the script. .PARAMETER Rescan Instructs Diskpart to rescan the system for available storage resources. .NOTES Name: New-VHD Author: Rich Kusak Created: 2009-05-27 LastEdit: 2009-10-26 10:06 #Requires -Version 2.0 #> function New-VHD { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] [string]$Path, [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false)] [int]$Maximum, [switch]$Fixed, [switch]$Mount, [switch]$NoConfirm, [string]$DiskpartScript = "$env:SystemDrive\DiskpartScript.txt", [switch]$Rescan ) begin { function InvokeDiskpart { Diskpart.exe /s $DiskpartScript } function TestMaximum { ## Validates the -Maximum parameter against the specified disk's available space ## $Drive = Split-Path $Path -Qualifier $LogicalDisk = Get-WmiObject win32_LogicalDisk -Filter "DeviceID = '$Drive'" $FreeSpace = [math]::Truncate(($LogicalDisk.FreeSpace)/1MB) $Percent = [math]::Round(($Maximum/$FreeSpace)*100,0) switch ($Maximum) { {$_ -gt 2088960} {throw "The -Maximum parameter value ""$Maximum"" exceeds the allowable VHD size."} {$_ -ge $FreeSpace -and $Fixed} {throw "The -Maximum parameter value ""$Maximum"" exceeds available disk space."} {$_ -ge $FreeSpace} { "" ; Write-Warning "The VHD maximum size exceeds available disk space." if (!$NoConfirm) { Write-Host "Are you sure you want to continue?" $Prompt = Read-Host "Type ""YES"" to continue or anything else to break" if ($Prompt -cne 'YES') { "" ; Write-Host "Script terminiated by user." ; "" return } } } {$Percent -ge 80} { "" ; Write-Warning "The VHD maximum size is $Percent% of the available disk space." if (!$NoConfirm) { Write-Host "Are you sure you want to continue?" $Prompt = Read-Host "Type ""YES"" to continue or anything else to break" if ($Prompt -cne 'YES') { "" ; Write-Host "Script terminiated by user." ; "" return } } } } ## Validate Operating System Version ## if (Get-WmiObject win32_OperatingSystem -Filter "Version < '6.1'") {throw "The script operation requires at least Windows 7."} } } process { TestMaximum ## Diskpart Script Content ## Here-String statement purposefully not indented ## @" $(if ($Rescan) {'Rescan'}) Create VDisk File="$Path" Maximum=$Maximum $(if ($Fixed) {'Type=Fixed'} else {'Type=Expandable'}) $(if ($Mount) {"Select VDisk File=""$Path""`nAttach VDisk"}) Exit "@ | Out-File -FilePath $DiskpartScript -Encoding ASCII -Force InvokeDiskpart } end { Remove-Item -Path $DiskpartScript -Force ; "" Write-Host "A new VHD has been created at ""$Path""." ; "" if ($Mount) {Write-Host "The VHD has been successfully mounted." ; ""} } } Export-ModuleMember -Function New-VHD <# .SYNOPSIS Set the system boot configuration to boot from a VHD. .DESCRIPTION This function wraps the consistancy of PowerShell around the BCDEdit tool. The Boot Configuration DataStore Editor (BCDEdit) is used to set the necessary boot configuration entry to optionally boot to a VHD during startup. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER Description Description for the boot configuration entry. .EXAMPLE Set-VHDBootConfiguration .NOTES Name: Set-VHDBootConfiguration Author: Rich Kusak Created: 2009-10-22 LastEdit: 2009-10-26 10:14 #Requires -Version 2.0 #> function Set-VHDBootConfiguration { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true)] [string]$Path, [Parameter(Position=1,Mandatory=$true)] [string]$Description ) begin { if (!(Test-Path -Path $Path -Include "*.vhd")) {throw "The path ""$Path"" is invalid or does not contain a VHD file."} $Drive = Split-Path -Path $Path -Qualifier $UnQualifiedPath = Split-Path -Path $Path -NoQualifier } process { $Copy = bcdedit /copy '{current}' /d $Description $CLSID = $Copy | ForEach-Object {$_.Remove(0,37).Replace(".","")} bcdedit /set $CLSID device vhd=[$Drive]""$UnQualifiedPath"" bcdedit /set $CLSID osdevice vhd=[$Drive]""$UnQualifiedPath"" bcdedit /set $CLSID detecthal on New-Object PSObject | Add-Member -MemberType NoteProperty -Name 'Identifier' -Value $CLSID -PassThru | } end { Write-Host "The VHD ""$Path"" has been prepared for boot operation." ; "" } } Export-ModuleMember -Function Set-VHDBootConfiguration <# .SYNOPSIS Test a VHD. .DESCRIPTION This script wraps the consistancy of PowerShell around the Diskpart utility. A Diskpart script is created to automate initializing a VHD. The script creates a partition, assigns a drive letter, and formats a mounted VHD. .PARAMETER Path Specifies the full path to the VHD file. .PARAMETER DiskpartScript Specifies the path location of the Diskpart script file. Default location is $env:SystemDrive This file is deleted at the conclusion of the script. .PARAMETER Rescan Instructs Diskpart to rescan the system for available storage resources. .NOTES Name: Test-VHD.ps1 Author: Rich Kusak Created: 2009-10-23 LastEdit: 2009-10-26 10:17 #Requires -Version 2.0 #> function Test-VHD { [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] [string]$Path, [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$false)] [string]$DiskpartScript = "$env:SystemDrive\DiskpartScript.txt", [switch]$Rescan ) begin { function InvokeDiskpart { Diskpart.exe /s $DiskpartScript } if (!(Test-Path -Path $Path -Include "*.vhd")) {throw "The path ""$Path"" is not valid or does not contain a VHD file."} } process { @" Select VDisk File="$Path" Detail VDisk "@ | Out-File -FilePath $DiskpartScript -Encoding ASCII -Force InvokeDiskpart } end { Remove-Item -Path $DiskpartScript -Force ; "" } } Export-ModuleMember -Function Test-VHD