├── Hyper-ConvertImage ├── Convert-WindowsImage.ps1 ├── Hyper-ConvertImage.psd1 └── Hyper-ConvertImage.psm1 ├── LICENSE └── README.md /Hyper-ConvertImage/Convert-WindowsImage.ps1: -------------------------------------------------------------------------------- 1 | 2 | function 3 | Convert-WindowsImage 4 | { 5 | <# 6 | .NOTES 7 | Copyright (c) Microsoft Corporation. All rights reserved. 8 | 9 | Use of this sample source code is subject to the terms of the Microsoft 10 | license agreement under which you licensed this sample source code. If 11 | you did not accept the terms of the license agreement, you are not 12 | authorized to use this sample source code. For the terms of the license, 13 | please see the license agreement between you and Microsoft or, if applicable, 14 | see the LICENSE.RTF on your install media or the root of your tools installation. 15 | THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. 16 | 17 | .SYNOPSIS 18 | Creates a bootable VHD(X) based on Windows 7 or Windows 8 installation media. 19 | 20 | .DESCRIPTION 21 | Creates a bootable VHD(X) based on Windows 7 or Windows 8 installation media. 22 | 23 | .PARAMETER SourcePath 24 | The complete path to the WIM or ISO file that will be converted to a Virtual Hard Disk. 25 | The ISO file must be valid Windows installation media to be recognized successfully. 26 | 27 | .PARAMETER CacheSource 28 | If the source WIM/ISO was copied locally, we delete it by default. 29 | Pass $true to cache the source image from the temp directory. 30 | 31 | .PARAMETER VHDPath 32 | The name and path of the Virtual Hard Disk to create. 33 | Omitting this parameter will create the Virtual Hard Disk is the current directory, (or, 34 | if specified by the -WorkingDirectory parameter, the working directory) and will automatically 35 | name the file in the following format: 36 | 37 | ....___. 38 | i.e.: 39 | 9200.0.amd64fre.winmain_win8rtm.120725-1247_client_professional_en-us.vhd(x) 40 | 41 | .PARAMETER WorkingDirectory 42 | Specifies the directory where the VHD(X) file should be generated. 43 | If specified along with -VHDPath, the -WorkingDirectory value is ignored. 44 | The default value is the current directory ($pwd). 45 | 46 | .PARAMETER TempDirectory 47 | Specifies the directory where the logs and ISO files should be placed. 48 | The default value is the temp directory ($env:Temp). 49 | 50 | .PARAMETER SizeBytes 51 | The size of the Virtual Hard Disk to create. 52 | For fixed disks, the VHD(X) file will be allocated all of this space immediately. 53 | For dynamic disks, this will be the maximum size that the VHD(X) can grow to. 54 | The default value is 40GB. 55 | 56 | .PARAMETER VHDFormat 57 | Specifies whether to create a VHD or VHDX formatted Virtual Hard Disk. 58 | The default is AUTO, which will create a VHD if using the BIOS disk layout or 59 | VHDX if using UEFI or WindowsToGo layouts. 60 | 61 | .PARAMETER DiskLayout 62 | Specifies whether to build the image for BIOS (MBR), UEFI (GPT), or WindowsToGo (MBR). 63 | Generation 1 VMs require BIOS (MBR) images. Generation 2 VMs require UEFI (GPT) images. 64 | Windows To Go images will boot in UEFI or BIOS but are not technically supported (upgrade 65 | doesn't work) 66 | 67 | .PARAMETER UnattendPath 68 | The complete path to an unattend.xml file that can be injected into the VHD(X). 69 | 70 | .PARAMETER Edition 71 | The name or image index of the image to apply from the WIM. 72 | 73 | .PARAMETER Passthru 74 | Specifies that the full path to the VHD(X) that is created should be 75 | returned on the pipeline. 76 | 77 | .PARAMETER BCDBoot 78 | By default, the version of BCDBOOT.EXE that is present in \Windows\System32 79 | is used by Convert-WindowsImage. If you need to specify an alternate version, 80 | use this parameter to do so. 81 | 82 | .PARAMETER MergeFolder 83 | Specifies additional MergeFolder path to be added to the root of the VHD(X) 84 | 85 | .PARAMETER BCDinVHD 86 | Specifies the purpose of the VHD(x). Use NativeBoot to skip cration of BCD store 87 | inside the VHD(x). Use VirtualMachine (or do not specify this option) to ensure 88 | the BCD store is created inside the VHD(x). 89 | 90 | .PARAMETER Driver 91 | Full path to driver(s) (.inf files) to inject to the OS inside the VHD(x). 92 | 93 | .PARAMETER ExpandOnNativeBoot 94 | Specifies whether to expand the VHD(x) to its maximum suze upon native boot. 95 | The default is True. Set to False to disable expansion. 96 | 97 | .PARAMETER RemoteDesktopEnable 98 | Enable Remote Desktop to connect to the OS inside the VHD(x) upon provisioning. 99 | Does not include Windows Firewall rules (firewall exceptions). The default is False. 100 | 101 | .PARAMETER Feature 102 | Enables specified Windows Feature(s). Note that you need to specify the Internal names 103 | understood by DISM and DISM CMDLets (e.g. NetFx3) instead of the "Friendly" names 104 | from Server Manager CMDLets (e.g. NET-Framework-Core). 105 | 106 | .PARAMETER Package 107 | Injects specified Windows Package(s). Accepts path to either a directory or individual 108 | CAB or MSU file. 109 | 110 | .PARAMETER ShowUI 111 | Specifies that the Graphical User Interface should be displayed. 112 | 113 | .PARAMETER EnableDebugger 114 | Configures kernel debugging for the VHD(X) being created. 115 | EnableDebugger takes a single argument which specifies the debugging transport to use. 116 | Valid transports are: None, Serial, 1394, USB, Network, Local. 117 | 118 | Depending on the type of transport selected, additional configuration parameters will become 119 | available. 120 | 121 | Serial: 122 | -ComPort - The COM port number to use while communicating with the debugger. 123 | The default value is 1 (indicating COM1). 124 | -BaudRate - The baud rate (in bps) to use while communicating with the debugger. 125 | The default value is 115200, valid values are: 126 | 9600, 19200, 38400, 56700, 115200 127 | 128 | 1394: 129 | -Channel - The 1394 channel used to communicate with the debugger. 130 | The default value is 10. 131 | 132 | USB: 133 | -Target - The target name used for USB debugging. 134 | The default value is "debugging". 135 | 136 | Network: 137 | -IPAddress - The IP address of the debugging host computer. 138 | -Port - The port on which to connect to the debugging host. 139 | The default value is 50000, with a minimum value of 49152. 140 | -Key - The key used to encrypt the connection. Only [0-9] and [a-z] are allowed. 141 | -nodhcp - Prevents the use of DHCP to obtain the target IP address. 142 | -newkey - Specifies that a new encryption key should be generated for the connection. 143 | 144 | .PARAMETER DismPath 145 | Full Path to an alternative version of the Dism.exe tool. The default is the current OS version. 146 | 147 | .PARAMETER ApplyEA 148 | Specifies that any EAs captured in the WIM should be applied to the VHD. 149 | The default is False. 150 | 151 | .EXAMPLE 152 | .\Convert-WindowsImage.ps1 -SourcePath D:\foo\install.wim -Edition Professional -WorkingDirectory D:\foo 153 | 154 | This command will create a 40GB dynamically expanding VHD in the D:\foo folder. 155 | The VHD will be based on the Professional edition from D:\foo\install.wim, 156 | and will be named automatically. 157 | 158 | .EXAMPLE 159 | .\Convert-WindowsImage.ps1 -SourcePath D:\foo\Win7SP1.iso -Edition Ultimate -VHDPath D:\foo\Win7_Ultimate_SP1.vhd 160 | 161 | This command will parse the ISO file D:\foo\Win7SP1.iso and try to locate 162 | \sources\install.wim. If that file is found, it will be used to create a 163 | dynamically-expanding 40GB VHD containing the Ultimate SKU, and will be 164 | named D:\foo\Win7_Ultimate_SP1.vhd 165 | 166 | .EXAMPLE 167 | .\Convert-WindowsImage.ps1 -SourcePath D:\foo\install.wim -Edition Professional -EnableDebugger Serial -ComPort 2 -BaudRate 38400 168 | 169 | This command will create a VHD from D:\foo\install.wim of the Professional SKU. 170 | Serial debugging will be enabled in the VHD via COM2 at a baud rate of 38400bps. 171 | 172 | .OUTPUTS 173 | System.IO.FileInfo 174 | #> 175 | #Requires -Version 3.0 176 | [CmdletBinding(DefaultParameterSetName="SRC", 177 | HelpURI="https://github.com/Microsoft/Virtualization-Documentation/tree/master/hyperv-tools/Convert-WindowsImage")] 178 | 179 | param( 180 | [Parameter(ParameterSetName="SRC", Mandatory=$true, ValueFromPipeline=$true)] 181 | [Alias("WIM")] 182 | [string] 183 | [ValidateNotNullOrEmpty()] 184 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 185 | $SourcePath, 186 | 187 | [Parameter(ParameterSetName="SRC")] 188 | [switch] 189 | $CacheSource = $false, 190 | 191 | [Parameter(ParameterSetName="SRC")] 192 | [Alias("SKU")] 193 | [string[]] 194 | [ValidateNotNullOrEmpty()] 195 | $Edition, 196 | 197 | [Parameter(ParameterSetName="SRC")] 198 | [Alias("WorkDir")] 199 | [string] 200 | [ValidateNotNullOrEmpty()] 201 | [ValidateScript({ Test-Path $_ })] 202 | $WorkingDirectory = $pwd, 203 | 204 | [Parameter(ParameterSetName="SRC")] 205 | [Alias("TempDir")] 206 | [string] 207 | [ValidateNotNullOrEmpty()] 208 | $TempDirectory = $env:Temp, 209 | 210 | [Parameter(ParameterSetName="SRC")] 211 | [Alias("VHD")] 212 | [string] 213 | [ValidateNotNullOrEmpty()] 214 | $VHDPath, 215 | 216 | [Parameter(ParameterSetName="SRC")] 217 | [Alias("Size")] 218 | [UInt64] 219 | [ValidateNotNullOrEmpty()] 220 | [ValidateRange(512MB, 64TB)] 221 | $SizeBytes = 25GB, 222 | 223 | [Parameter(ParameterSetName="SRC")] 224 | [Alias("Format")] 225 | [string] 226 | [ValidateNotNullOrEmpty()] 227 | [ValidateSet("VHD", "VHDX", "AUTO")] 228 | $VHDFormat = "AUTO", 229 | 230 | [Parameter(ParameterSetName="SRC")] 231 | [Alias("MergeFolder")] 232 | [string] 233 | [ValidateNotNullOrEmpty()] 234 | $MergeFolderPath = "", 235 | 236 | [Parameter(ParameterSetName="SRC", Mandatory=$true)] 237 | [Alias("Layout")] 238 | [string] 239 | [ValidateNotNullOrEmpty()] 240 | [ValidateSet("BIOS", "UEFI", "WindowsToGo")] 241 | $DiskLayout, 242 | 243 | [Parameter(ParameterSetName="SRC")] 244 | [string] 245 | [ValidateNotNullOrEmpty()] 246 | [ValidateSet("NativeBoot", "VirtualMachine")] 247 | $BCDinVHD = "VirtualMachine", 248 | 249 | [Parameter(ParameterSetName="SRC")] 250 | [Parameter(ParameterSetName="UI")] 251 | [string] 252 | $BCDBoot = "bcdboot.exe", 253 | 254 | [Parameter(ParameterSetName="SRC")] 255 | [Parameter(ParameterSetName="UI")] 256 | [string] 257 | [ValidateNotNullOrEmpty()] 258 | [ValidateSet("None", "Serial", "1394", "USB", "Local", "Network")] 259 | $EnableDebugger = "None", 260 | 261 | [Parameter(ParameterSetName="SRC")] 262 | [string[]] 263 | [ValidateNotNullOrEmpty()] 264 | $Feature, 265 | 266 | [Parameter(ParameterSetName="SRC")] 267 | [string[]] 268 | [ValidateNotNullOrEmpty()] 269 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 270 | $Driver, 271 | 272 | [Parameter(ParameterSetName="SRC")] 273 | [string[]] 274 | [ValidateNotNullOrEmpty()] 275 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 276 | $Package, 277 | 278 | [Parameter(ParameterSetName="SRC")] 279 | [switch] 280 | $ExpandOnNativeBoot = $true, 281 | 282 | [Parameter(ParameterSetName="SRC")] 283 | [switch] 284 | $RemoteDesktopEnable = $false, 285 | 286 | [Parameter(ParameterSetName="SRC")] 287 | [Alias("Unattend")] 288 | [string] 289 | [ValidateNotNullOrEmpty()] 290 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 291 | $UnattendPath, 292 | 293 | [Parameter(ParameterSetName="SRC")] 294 | [Parameter(ParameterSetName="UI")] 295 | [switch] 296 | $Passthru, 297 | 298 | [Parameter(ParameterSetName="SRC")] 299 | [string] 300 | [ValidateNotNullOrEmpty()] 301 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 302 | $DismPath, 303 | 304 | [Parameter(ParameterSetName="SRC")] 305 | [switch] 306 | $ApplyEA = $false, 307 | 308 | [Parameter(ParameterSetName="UI")] 309 | [switch] 310 | $ShowUI 311 | ) 312 | #region Code 313 | 314 | # Begin Dynamic Parameters 315 | # Create the parameters for the various types of debugging. 316 | DynamicParam 317 | { 318 | Set-StrictMode -version 3 319 | 320 | # Set up the dynamic parameters. 321 | # Dynamic parameters are only available if certain conditions are met, so they'll only show up 322 | # as valid parameters when those conditions apply. Here, the conditions are based on the value of 323 | # the EnableDebugger parameter. Depending on which of a set of values is the specified argument 324 | # for EnableDebugger, different parameters will light up, as outlined below. 325 | 326 | $parameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 327 | 328 | if (!(Test-Path Variable:Private:EnableDebugger)) 329 | { 330 | return $parameterDictionary 331 | } 332 | 333 | switch ($EnableDebugger) 334 | { 335 | "Serial" 336 | { 337 | #region ComPort 338 | 339 | $ComPortAttr = New-Object System.Management.Automation.ParameterAttribute 340 | $ComPortAttr.ParameterSetName = "__AllParameterSets" 341 | $ComPortAttr.Mandatory = $false 342 | 343 | $ComPortValidator = New-Object System.Management.Automation.ValidateRangeAttribute( 344 | 1, 345 | 10 # Is that a good maximum? 346 | ) 347 | 348 | $ComPortNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 349 | 350 | $ComPortAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 351 | $ComPortAttrCollection.Add($ComPortAttr) 352 | $ComPortAttrCollection.Add($ComPortValidator) 353 | $ComPortAttrCollection.Add($ComPortNotNull) 354 | 355 | $ComPort = New-Object System.Management.Automation.RuntimeDefinedParameter( 356 | "ComPort", 357 | [UInt16], 358 | $ComPortAttrCollection 359 | ) 360 | 361 | # By default, use COM1 362 | $ComPort.Value = 1 363 | $parameterDictionary.Add("ComPort", $ComPort) 364 | #endregion ComPort 365 | 366 | #region BaudRate 367 | $BaudRateAttr = New-Object System.Management.Automation.ParameterAttribute 368 | $BaudRateAttr.ParameterSetName = "__AllParameterSets" 369 | $BaudRateAttr.Mandatory = $false 370 | 371 | $BaudRateValidator = New-Object System.Management.Automation.ValidateSetAttribute( 372 | 9600, 19200,38400, 57600, 115200 373 | ) 374 | 375 | $BaudRateNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 376 | 377 | $BaudRateAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 378 | $BaudRateAttrCollection.Add($BaudRateAttr) 379 | $BaudRateAttrCollection.Add($BaudRateValidator) 380 | $BaudRateAttrCollection.Add($BaudRateNotNull) 381 | 382 | $BaudRate = New-Object System.Management.Automation.RuntimeDefinedParameter( 383 | "BaudRate", 384 | [UInt32], 385 | $BaudRateAttrCollection 386 | ) 387 | 388 | # By default, use 115,200. 389 | $BaudRate.Value = 115200 390 | $parameterDictionary.Add("BaudRate", $BaudRate) 391 | #endregion BaudRate 392 | 393 | break 394 | } 395 | 396 | "1394" 397 | { 398 | $ChannelAttr = New-Object System.Management.Automation.ParameterAttribute 399 | $ChannelAttr.ParameterSetName = "__AllParameterSets" 400 | $ChannelAttr.Mandatory = $false 401 | 402 | $ChannelValidator = New-Object System.Management.Automation.ValidateRangeAttribute( 403 | 0, 404 | 62 405 | ) 406 | 407 | $ChannelNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 408 | 409 | $ChannelAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 410 | $ChannelAttrCollection.Add($ChannelAttr) 411 | $ChannelAttrCollection.Add($ChannelValidator) 412 | $ChannelAttrCollection.Add($ChannelNotNull) 413 | 414 | $Channel = New-Object System.Management.Automation.RuntimeDefinedParameter( 415 | "Channel", 416 | [UInt16], 417 | $ChannelAttrCollection 418 | ) 419 | 420 | # By default, use channel 10 421 | $Channel.Value = 10 422 | $parameterDictionary.Add("Channel", $Channel) 423 | break 424 | } 425 | 426 | "USB" 427 | { 428 | $TargetAttr = New-Object System.Management.Automation.ParameterAttribute 429 | $TargetAttr.ParameterSetName = "__AllParameterSets" 430 | $TargetAttr.Mandatory = $false 431 | 432 | $TargetNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 433 | 434 | $TargetAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 435 | $TargetAttrCollection.Add($TargetAttr) 436 | $TargetAttrCollection.Add($TargetNotNull) 437 | 438 | $Target = New-Object System.Management.Automation.RuntimeDefinedParameter( 439 | "Target", 440 | [string], 441 | $TargetAttrCollection 442 | ) 443 | 444 | # By default, use target = "debugging" 445 | $Target.Value = "Debugging" 446 | $parameterDictionary.Add("Target", $Target) 447 | break 448 | } 449 | 450 | "Network" 451 | { 452 | #region IP 453 | $IpAttr = New-Object System.Management.Automation.ParameterAttribute 454 | $IpAttr.ParameterSetName = "__AllParameterSets" 455 | $IpAttr.Mandatory = $true 456 | 457 | $IpValidator = New-Object System.Management.Automation.ValidatePatternAttribute( 458 | "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b" 459 | ) 460 | $IpNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 461 | 462 | $IpAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 463 | $IpAttrCollection.Add($IpAttr) 464 | $IpAttrCollection.Add($IpValidator) 465 | $IpAttrCollection.Add($IpNotNull) 466 | 467 | $IP = New-Object System.Management.Automation.RuntimeDefinedParameter( 468 | "IPAddress", 469 | [string], 470 | $IpAttrCollection 471 | ) 472 | 473 | # There's no good way to set a default value for this. 474 | $parameterDictionary.Add("IPAddress", $IP) 475 | #endregion IP 476 | 477 | #region Port 478 | $PortAttr = New-Object System.Management.Automation.ParameterAttribute 479 | $PortAttr.ParameterSetName = "__AllParameterSets" 480 | $PortAttr.Mandatory = $false 481 | 482 | $PortValidator = New-Object System.Management.Automation.ValidateRangeAttribute( 483 | 49152, 484 | 50039 485 | ) 486 | 487 | $PortNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 488 | 489 | $PortAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 490 | $PortAttrCollection.Add($PortAttr) 491 | $PortAttrCollection.Add($PortValidator) 492 | $PortAttrCollection.Add($PortNotNull) 493 | 494 | 495 | $Port = New-Object System.Management.Automation.RuntimeDefinedParameter( 496 | "Port", 497 | [UInt16], 498 | $PortAttrCollection 499 | ) 500 | 501 | # By default, use port 50000 502 | $Port.Value = 50000 503 | $parameterDictionary.Add("Port", $Port) 504 | #endregion Port 505 | 506 | #region Key 507 | $KeyAttr = New-Object System.Management.Automation.ParameterAttribute 508 | $KeyAttr.ParameterSetName = "__AllParameterSets" 509 | $KeyAttr.Mandatory = $true 510 | 511 | $KeyValidator = New-Object System.Management.Automation.ValidatePatternAttribute( 512 | "\b([A-Z0-9]+).([A-Z0-9]+).([A-Z0-9]+).([A-Z0-9]+)\b" 513 | ) 514 | 515 | $KeyNotNull = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute 516 | 517 | $KeyAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 518 | $KeyAttrCollection.Add($KeyAttr) 519 | $KeyAttrCollection.Add($KeyValidator) 520 | $KeyAttrCollection.Add($KeyNotNull) 521 | 522 | $Key = New-Object System.Management.Automation.RuntimeDefinedParameter( 523 | "Key", 524 | [string], 525 | $KeyAttrCollection 526 | ) 527 | 528 | # Don't set a default key. 529 | $parameterDictionary.Add("Key", $Key) 530 | #endregion Key 531 | 532 | #region NoDHCP 533 | $NoDHCPAttr = New-Object System.Management.Automation.ParameterAttribute 534 | $NoDHCPAttr.ParameterSetName = "__AllParameterSets" 535 | $NoDHCPAttr.Mandatory = $false 536 | 537 | $NoDHCPAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 538 | $NoDHCPAttrCollection.Add($NoDHCPAttr) 539 | 540 | $NoDHCP = New-Object System.Management.Automation.RuntimeDefinedParameter( 541 | "NoDHCP", 542 | [switch], 543 | $NoDHCPAttrCollection 544 | ) 545 | 546 | $parameterDictionary.Add("NoDHCP", $NoDHCP) 547 | #endregion NoDHCP 548 | 549 | #region NewKey 550 | $NewKeyAttr = New-Object System.Management.Automation.ParameterAttribute 551 | $NewKeyAttr.ParameterSetName = "__AllParameterSets" 552 | $NewKeyAttr.Mandatory = $false 553 | 554 | $NewKeyAttrCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] 555 | $NewKeyAttrCollection.Add($NewKeyAttr) 556 | 557 | $NewKey = New-Object System.Management.Automation.RuntimeDefinedParameter( 558 | "NewKey", 559 | [switch], 560 | $NewKeyAttrCollection 561 | ) 562 | 563 | # Don't set a default key. 564 | $parameterDictionary.Add("NewKey", $NewKey) 565 | #endregion NewKey 566 | 567 | break 568 | } 569 | 570 | # There's nothing to do for local debugging. 571 | # Synthetic debugging is not yet implemented. 572 | 573 | default 574 | { 575 | break 576 | } 577 | } 578 | 579 | return $parameterDictionary 580 | } 581 | 582 | Begin 583 | { 584 | ########################################################################################## 585 | # Constants and Pseudo-Constants 586 | ########################################################################################## 587 | $PARTITION_STYLE_MBR = 0x00000000 # The default value 588 | $PARTITION_STYLE_GPT = 0x00000001 # Just in case... 589 | 590 | # Version information that can be populated by timebuild. 591 | $ScriptVersion = DATA 592 | { 593 | ConvertFrom-StringData -StringData @" 594 | Major = 10 595 | Minor = 0 596 | Build = 14278 597 | Qfe = 1000 598 | Branch = rs1_es_media 599 | Timestamp = 160201-1707 600 | Flavor = amd64fre 601 | "@ 602 | } 603 | 604 | $myVersion = "$($ScriptVersion.Major).$($ScriptVersion.Minor).$($ScriptVersion.Build).$($ScriptVersion.QFE).$($ScriptVersion.Flavor).$($ScriptVersion.Branch).$($ScriptVersion.Timestamp)" 605 | $scriptName = "Convert-WindowsImage" # Name of the script, obviously. 606 | $sessionKey = [Guid]::NewGuid().ToString() # Session key, used for keeping records unique between multiple runs. 607 | $logFolder = "$($TempDirectory)\$($scriptName)\$($sessionKey)" # Log folder path. 608 | $vhdMaxSize = 2040GB # Maximum size for VHD is ~2040GB. 609 | $vhdxMaxSize = 64TB # Maximum size for VHDX is ~64TB. 610 | $lowestSupportedVersion = New-Object Version "6.1" # The lowest supported *image* version; making sure we don't run against Vista/2k8. 611 | $lowestSupportedBuild = 9200 # The lowest supported *host* build. Set to Win8 CP. 612 | $transcripting = $false 613 | 614 | # Since we use the VHDFormat in output, make it uppercase. 615 | # We'll make it lowercase again when we use it as a file extension. 616 | $VHDFormat = $VHDFormat.ToUpper() 617 | ########################################################################################## 618 | # Here Strings 619 | ########################################################################################## 620 | 621 | # Banner text displayed during each run. 622 | $header = @" 623 | 624 | Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 10 625 | Copyright (C) Microsoft Corporation. All rights reserved. 626 | Version $myVersion 627 | 628 | "@ 629 | 630 | # Text used as the banner in the UI. 631 | $uiHeader = @" 632 | You can use the fields below to configure the VHD or VHDX that you want to create! 633 | "@ 634 | 635 | #region Helper Functions 636 | 637 | ########################################################################################## 638 | # Helper Functions 639 | ########################################################################################## 640 | 641 | <# 642 | Functions to mount and dismount registry hives. 643 | These hives will automatically be accessible via the HKLM:\ registry PSDrive. 644 | 645 | It should be noted that I have more confidence in using the RegLoadKey and 646 | RegUnloadKey Win32 APIs than I do using REG.EXE - it just seems like we should 647 | do things ourselves if we can, instead of using yet another binary. 648 | 649 | Consider this a TODO for future versions. 650 | #> 651 | Function Mount-RegistryHive 652 | { 653 | [CmdletBinding()] 654 | param( 655 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] 656 | [System.IO.FileInfo] 657 | [ValidateNotNullOrEmpty()] 658 | [ValidateScript({ $_.Exists })] 659 | $Hive 660 | ) 661 | 662 | $mountKey = [System.Guid]::NewGuid().ToString() 663 | $regPath = "REG.EXE" 664 | 665 | if (Test-Path HKLM:\$mountKey) 666 | { 667 | throw "The registry path already exists. I should just regenerate it, but I'm lazy." 668 | } 669 | 670 | $regArgs = ( 671 | "LOAD", 672 | "HKLM\$mountKey", 673 | $Hive.Fullname 674 | ) 675 | try 676 | { 677 | 678 | Run-Executable -Executable $regPath -Arguments $regArgs 679 | 680 | } 681 | catch 682 | { 683 | throw 684 | } 685 | 686 | # Set a global variable containing the name of the mounted registry key 687 | # so we can unmount it if there's an error. 688 | $global:mountedHive = $mountKey 689 | 690 | return $mountKey 691 | } 692 | 693 | ########################################################################################## 694 | 695 | Function Dismount-RegistryHive 696 | { 697 | [CmdletBinding()] 698 | param( 699 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] 700 | [string] 701 | [ValidateNotNullOrEmpty()] 702 | $HiveMountPoint 703 | ) 704 | 705 | $regPath = "REG.EXE" 706 | 707 | $regArgs = ( 708 | "UNLOAD", 709 | "HKLM\$($HiveMountPoint)" 710 | ) 711 | 712 | Run-Executable -Executable $regPath -Arguments $regArgs 713 | 714 | $global:mountedHive = $null 715 | } 716 | 717 | ########################################################################################## 718 | 719 | function 720 | Test-Admin 721 | { 722 | <# 723 | .SYNOPSIS 724 | Short function to determine whether the logged-on user is an administrator. 725 | 726 | .EXAMPLE 727 | Do you honestly need one? There are no parameters! 728 | 729 | .OUTPUTS 730 | $true if user is admin. 731 | $false if user is not an admin. 732 | #> 733 | [CmdletBinding()] 734 | param() 735 | 736 | $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) 737 | $isAdmin = $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) 738 | Write-W2VTrace "isUserAdmin? $isAdmin" 739 | 740 | return $isAdmin 741 | } 742 | 743 | ########################################################################################## 744 | 745 | function 746 | Get-WindowsBuildNumber 747 | { 748 | $os = Get-WmiObject -Class Win32_OperatingSystem 749 | return [int]($os.BuildNumber) 750 | } 751 | 752 | ########################################################################################## 753 | 754 | function 755 | Test-WindowsVersion 756 | { 757 | $isWin8 = ((Get-WindowsBuildNumber) -ge [int]$lowestSupportedBuild) 758 | 759 | Write-W2VTrace "is Windows 8 or Higher? $isWin8" 760 | return $isWin8 761 | } 762 | 763 | ########################################################################################## 764 | 765 | function 766 | Write-W2VInfo 767 | { 768 | # Function to make the Write-Host output a bit prettier. 769 | [CmdletBinding()] 770 | param( 771 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 772 | [string] 773 | [ValidateNotNullOrEmpty()] 774 | $text 775 | ) 776 | Write-Host "INFO : $($text)" 777 | } 778 | 779 | ########################################################################################## 780 | 781 | function 782 | Write-W2VTrace 783 | { 784 | # Function to make the Write-Verbose output... well... exactly the same as it was before. 785 | [CmdletBinding()] 786 | param( 787 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 788 | [string] 789 | [ValidateNotNullOrEmpty()] 790 | $text 791 | ) 792 | Write-Verbose $text 793 | } 794 | 795 | ########################################################################################## 796 | 797 | function 798 | Write-W2VError 799 | { 800 | # Function to make the Write-Host (NOT Write-Error) output prettier in the case of an error. 801 | [CmdletBinding()] 802 | param( 803 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 804 | [string] 805 | [ValidateNotNullOrEmpty()] 806 | $text 807 | ) 808 | Write-Host "ERROR : $($text)" -ForegroundColor (Get-Host).PrivateData.ErrorForegroundColor 809 | } 810 | 811 | ########################################################################################## 812 | 813 | function 814 | Write-W2VWarn 815 | { 816 | # Function to make the Write-Host (NOT Write-Warning) output prettier. 817 | [CmdletBinding()] 818 | param( 819 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 820 | [string] 821 | [ValidateNotNullOrEmpty()] 822 | $text 823 | ) 824 | Write-Host "WARN : $($text)" -ForegroundColor (Get-Host).PrivateData.WarningForegroundColor 825 | } 826 | 827 | ########################################################################################## 828 | 829 | function 830 | Run-Executable 831 | { 832 | <# 833 | .SYNOPSIS 834 | Runs an external executable file, and validates the error level. 835 | 836 | .PARAMETER Executable 837 | The path to the executable to run and monitor. 838 | 839 | .PARAMETER Arguments 840 | An array of arguments to pass to the executable when it's executed. 841 | 842 | .PARAMETER SuccessfulErrorCode 843 | The error code that means the executable ran successfully. 844 | The default value is 0. 845 | #> 846 | 847 | [CmdletBinding()] 848 | param( 849 | [Parameter(Mandatory=$true)] 850 | [string] 851 | [ValidateNotNullOrEmpty()] 852 | $Executable, 853 | 854 | [Parameter(Mandatory=$true)] 855 | [string[]] 856 | [ValidateNotNullOrEmpty()] 857 | $Arguments, 858 | 859 | [Parameter()] 860 | [int] 861 | [ValidateNotNullOrEmpty()] 862 | $SuccessfulErrorCode = 0 863 | 864 | ) 865 | 866 | Write-W2VTrace "Running $Executable $Arguments" 867 | $ret = Start-Process ` 868 | -FilePath $Executable ` 869 | -ArgumentList $Arguments ` 870 | -NoNewWindow ` 871 | -Wait ` 872 | -RedirectStandardOutput "$($TempDirectory)\$($scriptName)\$($sessionKey)\$($Executable)-StandardOutput.txt" ` 873 | -RedirectStandardError "$($TempDirectory)\$($scriptName)\$($sessionKey)\$($Executable)-StandardError.txt" ` 874 | -Passthru 875 | 876 | Write-W2VTrace "Return code was $($ret.ExitCode)." 877 | 878 | if ($ret.ExitCode -ne $SuccessfulErrorCode) 879 | { 880 | throw "$Executable failed with code $($ret.ExitCode)!" 881 | } 882 | } 883 | 884 | ########################################################################################## 885 | Function Test-IsNetworkLocation 886 | { 887 | <# 888 | .SYNOPSIS 889 | Determines whether or not a given path is a network location or a local drive. 890 | 891 | .DESCRIPTION 892 | Function to determine whether or not a specified path is a local path, a UNC path, 893 | or a mapped network drive. 894 | 895 | .PARAMETER Path 896 | The path that we need to figure stuff out about, 897 | #> 898 | 899 | [CmdletBinding()] 900 | param( 901 | [Parameter(ValueFromPipeLine = $true)] 902 | [string] 903 | [ValidateNotNullOrEmpty()] 904 | $Path 905 | ) 906 | 907 | $result = $false 908 | 909 | if ([bool]([URI]$Path).IsUNC) 910 | { 911 | $result = $true 912 | } 913 | else 914 | { 915 | $driveInfo = [IO.DriveInfo]((Resolve-Path $Path).Path) 916 | 917 | if ($driveInfo.DriveType -eq "Network") 918 | { 919 | $result = $true 920 | } 921 | } 922 | 923 | return $result 924 | } 925 | ########################################################################################## 926 | 927 | #endregion Helper Functions 928 | } 929 | 930 | Process 931 | { 932 | Write-Host $header 933 | 934 | $disk = $null 935 | $openWim = $null 936 | $openIso = $null 937 | $openImage = $null 938 | $vhdFinalName = $null 939 | $vhdFinalPath = $null 940 | $mountedHive = $null 941 | $isoPath = $null 942 | $tempSource = $null 943 | 944 | if (Get-Command Get-WindowsOptionalFeature -ErrorAction SilentlyContinue) 945 | { 946 | try 947 | { 948 | $hyperVEnabled = $((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).State -eq "Enabled") 949 | } 950 | catch 951 | { 952 | # WinPE DISM does not support online queries. This will throw on non-WinPE machines 953 | $winpeVersion = (Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\WinPE').Version 954 | 955 | Write-W2VInfo "Running WinPE version $winpeVersion" 956 | 957 | $hyperVEnabled = $false 958 | } 959 | } 960 | else 961 | { 962 | $hyperVEnabled = $false 963 | } 964 | 965 | $vhd = @() 966 | 967 | try 968 | { 969 | # Create log folder 970 | if (Test-Path $logFolder) 971 | { 972 | $null = rd $logFolder -Force -Recurse 973 | } 974 | 975 | $null = md $logFolder -Force 976 | 977 | # Try to start transcripting. If it's already running, we'll get an exception and swallow it. 978 | try 979 | { 980 | $null = Start-Transcript -Path (Join-Path $logFolder "Convert-WindowsImageTranscript.txt") -Force -ErrorAction SilentlyContinue 981 | $transcripting = $true 982 | } 983 | catch 984 | { 985 | Write-W2VWarn "Transcription is already running. No Convert-WindowsImage-specific transcript will be created." 986 | $transcripting = $false 987 | } 988 | 989 | # 990 | # Add types 991 | # 992 | Add-WindowsImageTypes 993 | 994 | # Check to make sure we're running as Admin. 995 | if (!(Test-Admin)) 996 | { 997 | throw "Images can only be applied by an administrator. Please launch PowerShell elevated and run this script again." 998 | } 999 | 1000 | # Check to make sure we're running on Win8. 1001 | if (!(Test-WindowsVersion)) 1002 | { 1003 | throw "$scriptName requires Windows 8 Consumer Preview or higher. Please use WIM2VHD.WSF (http://code.msdn.microsoft.com/wim2vhd) if you need to create VHDs from Windows 7." 1004 | } 1005 | 1006 | # Resolve the path for the unattend file. 1007 | if (![string]::IsNullOrEmpty($UnattendPath)) 1008 | { 1009 | $UnattendPath = (Resolve-Path $UnattendPath).Path 1010 | } 1011 | 1012 | if ($ShowUI) 1013 | { 1014 | 1015 | Write-W2VInfo "Launching UI..." 1016 | Add-Type -AssemblyName System.Drawing,System.Windows.Forms 1017 | 1018 | #region Form Objects 1019 | $frmMain = New-Object System.Windows.Forms.Form 1020 | $groupBox4 = New-Object System.Windows.Forms.GroupBox 1021 | $btnGo = New-Object System.Windows.Forms.Button 1022 | $groupBox3 = New-Object System.Windows.Forms.GroupBox 1023 | $txtVhdName = New-Object System.Windows.Forms.TextBox 1024 | $label6 = New-Object System.Windows.Forms.Label 1025 | $btnWrkBrowse = New-Object System.Windows.Forms.Button 1026 | $cmbVhdSizeUnit = New-Object System.Windows.Forms.ComboBox 1027 | $numVhdSize = New-Object System.Windows.Forms.NumericUpDown 1028 | $cmbVhdFormat = New-Object System.Windows.Forms.ComboBox 1029 | $label5 = New-Object System.Windows.Forms.Label 1030 | $txtWorkingDirectory = New-Object System.Windows.Forms.TextBox 1031 | $label4 = New-Object System.Windows.Forms.Label 1032 | $label3 = New-Object System.Windows.Forms.Label 1033 | $label2 = New-Object System.Windows.Forms.Label 1034 | $label7 = New-Object System.Windows.Forms.Label 1035 | $txtUnattendFile = New-Object System.Windows.Forms.TextBox 1036 | $btnUnattendBrowse = New-Object System.Windows.Forms.Button 1037 | $groupBox2 = New-Object System.Windows.Forms.GroupBox 1038 | $cmbSkuList = New-Object System.Windows.Forms.ComboBox 1039 | $label1 = New-Object System.Windows.Forms.Label 1040 | $groupBox1 = New-Object System.Windows.Forms.GroupBox 1041 | $txtSourcePath = New-Object System.Windows.Forms.TextBox 1042 | $btnBrowseWim = New-Object System.Windows.Forms.Button 1043 | $openFileDialog1 = New-Object System.Windows.Forms.OpenFileDialog 1044 | $openFolderDialog1 = New-Object System.Windows.Forms.FolderBrowserDialog 1045 | $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState 1046 | 1047 | #endregion Form Objects 1048 | 1049 | #region Event scriptblocks. 1050 | 1051 | $btnGo_OnClick = { 1052 | $frmMain.Close() 1053 | } 1054 | 1055 | $btnWrkBrowse_OnClick = { 1056 | $openFolderDialog1.RootFolder = "Desktop" 1057 | $openFolderDialog1.Description = "Select the folder you'd like your VHD(X) to be created in." 1058 | $openFolderDialog1.SelectedPath = $WorkingDirectory 1059 | 1060 | $ret = $openFolderDialog1.ShowDialog() 1061 | 1062 | if ($ret -ilike "ok") 1063 | { 1064 | $WorkingDirectory = $txtWorkingDirectory = $openFolderDialog1.SelectedPath 1065 | Write-W2VInfo "Selected Working Directory is $WorkingDirectory..." 1066 | } 1067 | } 1068 | 1069 | $btnUnattendBrowse_OnClick = { 1070 | $openFileDialog1.InitialDirectory = $pwd 1071 | $openFileDialog1.Filter = "XML files (*.xml)|*.XML|All files (*.*)|*.*" 1072 | $openFileDialog1.FilterIndex = 1 1073 | $openFileDialog1.CheckFileExists = $true 1074 | $openFileDialog1.CheckPathExists = $true 1075 | $openFileDialog1.FileName = $null 1076 | $openFileDialog1.ShowHelp = $false 1077 | $openFileDialog1.Title = "Select an unattend file..." 1078 | 1079 | $ret = $openFileDialog1.ShowDialog() 1080 | 1081 | if ($ret -ilike "ok") 1082 | { 1083 | $UnattendPath = $txtUnattendFile.Text = $openFileDialog1.FileName 1084 | } 1085 | } 1086 | 1087 | $btnBrowseWim_OnClick = { 1088 | $openFileDialog1.InitialDirectory = $pwd 1089 | $openFileDialog1.Filter = "All compatible files (*.ISO, *.WIM)|*.ISO;*.WIM|All files (*.*)|*.*" 1090 | $openFileDialog1.FilterIndex = 1 1091 | $openFileDialog1.CheckFileExists = $true 1092 | $openFileDialog1.CheckPathExists = $true 1093 | $openFileDialog1.FileName = $null 1094 | $openFileDialog1.ShowHelp = $false 1095 | $openFileDialog1.Title = "Select a source file..." 1096 | 1097 | $ret = $openFileDialog1.ShowDialog() 1098 | 1099 | if ($ret -ilike "ok") 1100 | { 1101 | 1102 | if (([IO.FileInfo]$openFileDialog1.FileName).Extension -ilike ".iso") 1103 | { 1104 | 1105 | if (Test-IsNetworkLocation $openFileDialog1.FileName) 1106 | { 1107 | Write-W2VInfo "Copying ISO $(Split-Path $openFileDialog1.FileName -Leaf) to temp folder..." 1108 | Write-W2VWarn "The UI may become non-responsive while this copy takes place..." 1109 | Copy-Item -Path $openFileDialog1.FileName -Destination $TempDirectory -Force 1110 | $openFileDialog1.FileName = "$($TempDirectory)\$(Split-Path $openFileDialog1.FileName -Leaf)" 1111 | } 1112 | 1113 | $txtSourcePath.Text = $isoPath = (Resolve-Path $openFileDialog1.FileName).Path 1114 | Write-W2VInfo "Opening ISO $(Split-Path $isoPath -Leaf)..." 1115 | 1116 | $openIso = Mount-DiskImage -ImagePath $isoPath -StorageType ISO -PassThru 1117 | 1118 | # Refresh the DiskImage object so we can get the real information about it. I assume this is a bug. 1119 | $openIso = Get-DiskImage -ImagePath $isoPath 1120 | $driveLetter = ($openIso | Get-Volume).DriveLetter 1121 | 1122 | $script:SourcePath = "$($driveLetter):\sources\install.wim" 1123 | 1124 | # Check to see if there's a WIM file we can muck about with. 1125 | Write-W2VInfo "Looking for $($SourcePath)..." 1126 | if (!(Test-Path $SourcePath)) 1127 | { 1128 | throw "The specified ISO does not appear to be valid Windows installation media." 1129 | } 1130 | } 1131 | else 1132 | { 1133 | $txtSourcePath.Text = $script:SourcePath = $openFileDialog1.FileName 1134 | } 1135 | 1136 | # Check to see if the WIM is local, or on a network location. If the latter, copy it locally. 1137 | if (Test-IsNetworkLocation $SourcePath) 1138 | { 1139 | Write-W2VInfo "Copying WIM $(Split-Path $SourcePath -Leaf) to temp folder..." 1140 | Write-W2VWarn "The UI may become non-responsive while this copy takes place..." 1141 | Copy-Item -Path $SourcePath -Destination $TempDirectory -Force 1142 | $txtSourcePath.Text = $script:SourcePath = "$($TempDirectory)\$(Split-Path $SourcePath -Leaf)" 1143 | } 1144 | 1145 | $script:SourcePath = (Resolve-Path $SourcePath).Path 1146 | 1147 | Write-W2VInfo "Scanning WIM metadata..." 1148 | 1149 | $tempOpenWim = $null 1150 | 1151 | try 1152 | { 1153 | $tempOpenWim = New-Object WIM2VHD.WimFile $SourcePath 1154 | 1155 | # Let's see if we're running against an unstaged build. If we are, we need to blow up. 1156 | if ($tempOpenWim.ImageNames.Contains("Windows Longhorn Client") -or 1157 | $tempOpenWim.ImageNames.Contains("Windows Longhorn Server") -or 1158 | $tempOpenWim.ImageNames.Contains("Windows Longhorn Server Core")) 1159 | { 1160 | [Windows.Forms.MessageBox]::Show( 1161 | "Convert-WindowsImage cannot run against unstaged builds. Please try again with a staged build.", 1162 | "WIM is incompatible!", 1163 | "OK", 1164 | "Error" 1165 | ) 1166 | 1167 | return 1168 | } 1169 | else 1170 | { 1171 | $tempOpenWim.Images | %{ $cmbSkuList.Items.Add($_.ImageFlags) } 1172 | $cmbSkuList.SelectedIndex = 0 1173 | } 1174 | 1175 | } 1176 | catch 1177 | { 1178 | throw "Unable to load WIM metadata!" 1179 | } 1180 | finally 1181 | { 1182 | $tempOpenWim.Close() 1183 | Write-W2VTrace "Closing WIM metadata..." 1184 | } 1185 | } 1186 | } 1187 | 1188 | $OnLoadForm_StateCorrection = { 1189 | 1190 | # Correct the initial state of the form to prevent the .Net maximized form issue 1191 | $frmMain.WindowState = $InitialFormWindowState 1192 | } 1193 | 1194 | #endregion Event scriptblocks 1195 | 1196 | # Figure out VHD size and size unit. 1197 | $unit = $null 1198 | switch ([Math]::Round($SizeBytes.ToString().Length / 3)) 1199 | { 1200 | 3 { $unit = "MB"; break } 1201 | 4 { $unit = "GB"; break } 1202 | 5 { $unit = "TB"; break } 1203 | default { $unit = ""; break } 1204 | } 1205 | 1206 | $quantity = Invoke-Expression -Command "$($SizeBytes) / 1$($unit)" 1207 | 1208 | #region Form Code 1209 | #region frmMain 1210 | $frmMain.DataBindings.DefaultDataSourceUpdateMode = 0 1211 | $System_Drawing_Size = New-Object System.Drawing.Size 1212 | $System_Drawing_Size.Height = 579 1213 | $System_Drawing_Size.Width = 512 1214 | $frmMain.ClientSize = $System_Drawing_Size 1215 | $frmMain.Font = New-Object System.Drawing.Font("Segoe UI",10,0,3,1) 1216 | $frmMain.FormBorderStyle = 1 1217 | $frmMain.MaximizeBox = $False 1218 | $frmMain.MinimizeBox = $False 1219 | $frmMain.Name = "frmMain" 1220 | $frmMain.StartPosition = 1 1221 | $frmMain.Text = "Convert-WindowsImage UI" 1222 | #endregion frmMain 1223 | 1224 | #region groupBox4 1225 | $groupBox4.DataBindings.DefaultDataSourceUpdateMode = 0 1226 | $System_Drawing_Point = New-Object System.Drawing.Point 1227 | $System_Drawing_Point.X = 10 1228 | $System_Drawing_Point.Y = 498 1229 | $groupBox4.Location = $System_Drawing_Point 1230 | $groupBox4.Name = "groupBox4" 1231 | $System_Drawing_Size = New-Object System.Drawing.Size 1232 | $System_Drawing_Size.Height = 69 1233 | $System_Drawing_Size.Width = 489 1234 | $groupBox4.Size = $System_Drawing_Size 1235 | $groupBox4.TabIndex = 8 1236 | $groupBox4.TabStop = $False 1237 | $groupBox4.Text = "4. Make the VHD!" 1238 | 1239 | $frmMain.Controls.Add($groupBox4) 1240 | #endregion groupBox4 1241 | 1242 | #region btnGo 1243 | $btnGo.DataBindings.DefaultDataSourceUpdateMode = 0 1244 | $System_Drawing_Point = New-Object System.Drawing.Point 1245 | $System_Drawing_Point.X = 39 1246 | $System_Drawing_Point.Y = 24 1247 | $btnGo.Location = $System_Drawing_Point 1248 | $btnGo.Name = "btnGo" 1249 | $System_Drawing_Size = New-Object System.Drawing.Size 1250 | $System_Drawing_Size.Height = 33 1251 | $System_Drawing_Size.Width = 415 1252 | $btnGo.Size = $System_Drawing_Size 1253 | $btnGo.TabIndex = 0 1254 | $btnGo.Text = "&Make my VHD" 1255 | $btnGo.UseVisualStyleBackColor = $True 1256 | $btnGo.DialogResult = "OK" 1257 | $btnGo.add_Click($btnGo_OnClick) 1258 | 1259 | $groupBox4.Controls.Add($btnGo) 1260 | $frmMain.AcceptButton = $btnGo 1261 | #endregion btnGo 1262 | 1263 | #region groupBox3 1264 | $groupBox3.DataBindings.DefaultDataSourceUpdateMode = 0 1265 | $System_Drawing_Point = New-Object System.Drawing.Point 1266 | $System_Drawing_Point.X = 10 1267 | $System_Drawing_Point.Y = 243 1268 | $groupBox3.Location = $System_Drawing_Point 1269 | $groupBox3.Name = "groupBox3" 1270 | $System_Drawing_Size = New-Object System.Drawing.Size 1271 | $System_Drawing_Size.Height = 245 1272 | $System_Drawing_Size.Width = 489 1273 | $groupBox3.Size = $System_Drawing_Size 1274 | $groupBox3.TabIndex = 7 1275 | $groupBox3.TabStop = $False 1276 | $groupBox3.Text = "3. Choose configuration options" 1277 | 1278 | $frmMain.Controls.Add($groupBox3) 1279 | #endregion groupBox3 1280 | 1281 | #region txtVhdName 1282 | $txtVhdName.DataBindings.DefaultDataSourceUpdateMode = 0 1283 | $System_Drawing_Point = New-Object System.Drawing.Point 1284 | $System_Drawing_Point.X = 25 1285 | $System_Drawing_Point.Y = 150 1286 | $txtVhdName.Location = $System_Drawing_Point 1287 | $txtVhdName.Name = "txtVhdName" 1288 | $System_Drawing_Size = New-Object System.Drawing.Size 1289 | $System_Drawing_Size.Height = 25 1290 | $System_Drawing_Size.Width = 418 1291 | $txtVhdName.Size = $System_Drawing_Size 1292 | $txtVhdName.TabIndex = 10 1293 | 1294 | $groupBox3.Controls.Add($txtVhdName) 1295 | #endregion txtVhdName 1296 | 1297 | #region txtUnattendFile 1298 | $txtUnattendFile.DataBindings.DefaultDataSourceUpdateMode = 0 1299 | $System_Drawing_Point = New-Object System.Drawing.Point 1300 | $System_Drawing_Point.X = 25 1301 | $System_Drawing_Point.Y = 198 1302 | $txtUnattendFile.Location = $System_Drawing_Point 1303 | $txtUnattendFile.Name = "txtUnattendFile" 1304 | $System_Drawing_Size = New-Object System.Drawing.Size 1305 | $System_Drawing_Size.Height = 25 1306 | $System_Drawing_Size.Width = 418 1307 | $txtUnattendFile.Size = $System_Drawing_Size 1308 | $txtUnattendFile.TabIndex = 11 1309 | 1310 | $groupBox3.Controls.Add($txtUnattendFile) 1311 | #endregion txtUnattendFile 1312 | 1313 | #region label7 1314 | $label7.DataBindings.DefaultDataSourceUpdateMode = 0 1315 | $System_Drawing_Point = New-Object System.Drawing.Point 1316 | $System_Drawing_Point.X = 23 1317 | $System_Drawing_Point.Y = 180 1318 | $label7.Location = $System_Drawing_Point 1319 | $label7.Name = "label7" 1320 | $System_Drawing_Size = New-Object System.Drawing.Size 1321 | $System_Drawing_Size.Height = 23 1322 | $System_Drawing_Size.Width = 175 1323 | $label7.Size = $System_Drawing_Size 1324 | $label7.Text = "Unattend File (Optional)" 1325 | 1326 | $groupBox3.Controls.Add($label7) 1327 | #endregion label7 1328 | 1329 | #region label6 1330 | $label6.DataBindings.DefaultDataSourceUpdateMode = 0 1331 | $System_Drawing_Point = New-Object System.Drawing.Point 1332 | $System_Drawing_Point.X = 23 1333 | $System_Drawing_Point.Y = 132 1334 | $label6.Location = $System_Drawing_Point 1335 | $label6.Name = "label6" 1336 | $System_Drawing_Size = New-Object System.Drawing.Size 1337 | $System_Drawing_Size.Height = 23 1338 | $System_Drawing_Size.Width = 175 1339 | $label6.Size = $System_Drawing_Size 1340 | $label6.Text = "VHD Name (Optional)" 1341 | 1342 | $groupBox3.Controls.Add($label6) 1343 | #endregion label6 1344 | 1345 | #region btnUnattendBrowse 1346 | $btnUnattendBrowse.DataBindings.DefaultDataSourceUpdateMode = 0 1347 | $System_Drawing_Point = New-Object System.Drawing.Point 1348 | $System_Drawing_Point.X = 449 1349 | $System_Drawing_Point.Y = 199 1350 | $btnUnattendBrowse.Location = $System_Drawing_Point 1351 | $btnUnattendBrowse.Name = "btnUnattendBrowse" 1352 | $System_Drawing_Size = New-Object System.Drawing.Size 1353 | $System_Drawing_Size.Height = 25 1354 | $System_Drawing_Size.Width = 27 1355 | $btnUnattendBrowse.Size = $System_Drawing_Size 1356 | $btnUnattendBrowse.TabIndex = 9 1357 | $btnUnattendBrowse.Text = "..." 1358 | $btnUnattendBrowse.UseVisualStyleBackColor = $True 1359 | $btnUnattendBrowse.add_Click($btnUnattendBrowse_OnClick) 1360 | 1361 | $groupBox3.Controls.Add($btnUnattendBrowse) 1362 | #endregion btnUnattendBrowse 1363 | 1364 | #region btnWrkBrowse 1365 | $btnWrkBrowse.DataBindings.DefaultDataSourceUpdateMode = 0 1366 | $System_Drawing_Point = New-Object System.Drawing.Point 1367 | $System_Drawing_Point.X = 449 1368 | $System_Drawing_Point.Y = 98 1369 | $btnWrkBrowse.Location = $System_Drawing_Point 1370 | $btnWrkBrowse.Name = "btnWrkBrowse" 1371 | $System_Drawing_Size = New-Object System.Drawing.Size 1372 | $System_Drawing_Size.Height = 25 1373 | $System_Drawing_Size.Width = 27 1374 | $btnWrkBrowse.Size = $System_Drawing_Size 1375 | $btnWrkBrowse.TabIndex = 9 1376 | $btnWrkBrowse.Text = "..." 1377 | $btnWrkBrowse.UseVisualStyleBackColor = $True 1378 | $btnWrkBrowse.add_Click($btnWrkBrowse_OnClick) 1379 | 1380 | $groupBox3.Controls.Add($btnWrkBrowse) 1381 | #endregion btnWrkBrowse 1382 | 1383 | #region cmbVhdSizeUnit 1384 | $cmbVhdSizeUnit.DataBindings.DefaultDataSourceUpdateMode = 0 1385 | $cmbVhdSizeUnit.FormattingEnabled = $True 1386 | $cmbVhdSizeUnit.Items.Add("MB") | Out-Null 1387 | $cmbVhdSizeUnit.Items.Add("GB") | Out-Null 1388 | $cmbVhdSizeUnit.Items.Add("TB") | Out-Null 1389 | $System_Drawing_Point = New-Object System.Drawing.Point 1390 | $System_Drawing_Point.X = 409 1391 | $System_Drawing_Point.Y = 42 1392 | $cmbVhdSizeUnit.Location = $System_Drawing_Point 1393 | $cmbVhdSizeUnit.Name = "cmbVhdSizeUnit" 1394 | $System_Drawing_Size = New-Object System.Drawing.Size 1395 | $System_Drawing_Size.Height = 25 1396 | $System_Drawing_Size.Width = 67 1397 | $cmbVhdSizeUnit.Size = $System_Drawing_Size 1398 | $cmbVhdSizeUnit.TabIndex = 5 1399 | $cmbVhdSizeUnit.Text = $unit 1400 | 1401 | $groupBox3.Controls.Add($cmbVhdSizeUnit) 1402 | #endregion cmbVhdSizeUnit 1403 | 1404 | #region numVhdSize 1405 | $numVhdSize.DataBindings.DefaultDataSourceUpdateMode = 0 1406 | $System_Drawing_Point = New-Object System.Drawing.Point 1407 | $System_Drawing_Point.X = 340 1408 | $System_Drawing_Point.Y = 42 1409 | $numVhdSize.Location = $System_Drawing_Point 1410 | $numVhdSize.Name = "numVhdSize" 1411 | $System_Drawing_Size = New-Object System.Drawing.Size 1412 | $System_Drawing_Size.Height = 25 1413 | $System_Drawing_Size.Width = 63 1414 | $numVhdSize.Size = $System_Drawing_Size 1415 | $numVhdSize.TabIndex = 4 1416 | $numVhdSize.Value = $quantity 1417 | 1418 | $groupBox3.Controls.Add($numVhdSize) 1419 | #endregion numVhdSize 1420 | 1421 | #region cmbVhdFormat 1422 | $cmbVhdFormat.DataBindings.DefaultDataSourceUpdateMode = 0 1423 | $cmbVhdFormat.FormattingEnabled = $True 1424 | $cmbVhdFormat.Items.Add("VHD") | Out-Null 1425 | $cmbVhdFormat.Items.Add("VHDX") | Out-Null 1426 | $System_Drawing_Point = New-Object System.Drawing.Point 1427 | $System_Drawing_Point.X = 25 1428 | $System_Drawing_Point.Y = 42 1429 | $cmbVhdFormat.Location = $System_Drawing_Point 1430 | $cmbVhdFormat.Name = "cmbVhdFormat" 1431 | $System_Drawing_Size = New-Object System.Drawing.Size 1432 | $System_Drawing_Size.Height = 25 1433 | $System_Drawing_Size.Width = 136 1434 | $cmbVhdFormat.Size = $System_Drawing_Size 1435 | $cmbVhdFormat.TabIndex = 0 1436 | $cmbVhdFormat.Text = $VHDFormat 1437 | 1438 | $groupBox3.Controls.Add($cmbVhdFormat) 1439 | #endregion cmbVhdFormat 1440 | 1441 | #region label5 1442 | $label5.DataBindings.DefaultDataSourceUpdateMode = 0 1443 | $System_Drawing_Point = New-Object System.Drawing.Point 1444 | $System_Drawing_Point.X = 23 1445 | $System_Drawing_Point.Y = 76 1446 | $label5.Location = $System_Drawing_Point 1447 | $label5.Name = "label5" 1448 | $System_Drawing_Size = New-Object System.Drawing.Size 1449 | $System_Drawing_Size.Height = 23 1450 | $System_Drawing_Size.Width = 264 1451 | $label5.Size = $System_Drawing_Size 1452 | $label5.TabIndex = 8 1453 | $label5.Text = "Working Directory" 1454 | 1455 | $groupBox3.Controls.Add($label5) 1456 | #endregion label5 1457 | 1458 | #region txtWorkingDirectory 1459 | $txtWorkingDirectory.DataBindings.DefaultDataSourceUpdateMode = 0 1460 | $System_Drawing_Point = New-Object System.Drawing.Point 1461 | $System_Drawing_Point.X = 25 1462 | $System_Drawing_Point.Y = 99 1463 | $txtWorkingDirectory.Location = $System_Drawing_Point 1464 | $txtWorkingDirectory.Name = "txtWorkingDirectory" 1465 | $System_Drawing_Size = New-Object System.Drawing.Size 1466 | $System_Drawing_Size.Height = 25 1467 | $System_Drawing_Size.Width = 418 1468 | $txtWorkingDirectory.Size = $System_Drawing_Size 1469 | $txtWorkingDirectory.TabIndex = 7 1470 | $txtWorkingDirectory.Text = $WorkingDirectory 1471 | 1472 | $groupBox3.Controls.Add($txtWorkingDirectory) 1473 | #endregion txtWorkingDirectory 1474 | 1475 | #region label4 1476 | $label4.DataBindings.DefaultDataSourceUpdateMode = 0 1477 | $System_Drawing_Point = New-Object System.Drawing.Point 1478 | $System_Drawing_Point.X = 340 1479 | $System_Drawing_Point.Y = 21 1480 | $label4.Location = $System_Drawing_Point 1481 | $label4.Name = "label4" 1482 | $System_Drawing_Size = New-Object System.Drawing.Size 1483 | $System_Drawing_Size.Height = 27 1484 | $System_Drawing_Size.Width = 86 1485 | $label4.Size = $System_Drawing_Size 1486 | $label4.TabIndex = 6 1487 | $label4.Text = "VHD Size" 1488 | 1489 | $groupBox3.Controls.Add($label4) 1490 | #endregion label4 1491 | 1492 | #region label3 1493 | $label3.DataBindings.DefaultDataSourceUpdateMode = 0 1494 | $System_Drawing_Point = New-Object System.Drawing.Point 1495 | $System_Drawing_Point.X = 176 1496 | $System_Drawing_Point.Y = 21 1497 | $label3.Location = $System_Drawing_Point 1498 | $label3.Name = "label3" 1499 | $System_Drawing_Size = New-Object System.Drawing.Size 1500 | $System_Drawing_Size.Height = 27 1501 | $System_Drawing_Size.Width = 92 1502 | $label3.Size = $System_Drawing_Size 1503 | $label3.TabIndex = 3 1504 | $label3.Text = "VHD Type" 1505 | 1506 | $groupBox3.Controls.Add($label3) 1507 | #endregion label3 1508 | 1509 | #region label2 1510 | $label2.DataBindings.DefaultDataSourceUpdateMode = 0 1511 | $System_Drawing_Point = New-Object System.Drawing.Point 1512 | $System_Drawing_Point.X = 25 1513 | $System_Drawing_Point.Y = 21 1514 | $label2.Location = $System_Drawing_Point 1515 | $label2.Name = "label2" 1516 | $System_Drawing_Size = New-Object System.Drawing.Size 1517 | $System_Drawing_Size.Height = 30 1518 | $System_Drawing_Size.Width = 118 1519 | $label2.Size = $System_Drawing_Size 1520 | $label2.TabIndex = 1 1521 | $label2.Text = "VHD Format" 1522 | 1523 | $groupBox3.Controls.Add($label2) 1524 | #endregion label2 1525 | 1526 | #region groupBox2 1527 | $groupBox2.DataBindings.DefaultDataSourceUpdateMode = 0 1528 | $System_Drawing_Point = New-Object System.Drawing.Point 1529 | $System_Drawing_Point.X = 10 1530 | $System_Drawing_Point.Y = 169 1531 | $groupBox2.Location = $System_Drawing_Point 1532 | $groupBox2.Name = "groupBox2" 1533 | $System_Drawing_Size = New-Object System.Drawing.Size 1534 | $System_Drawing_Size.Height = 68 1535 | $System_Drawing_Size.Width = 490 1536 | $groupBox2.Size = $System_Drawing_Size 1537 | $groupBox2.TabIndex = 6 1538 | $groupBox2.TabStop = $False 1539 | $groupBox2.Text = "2. Choose a SKU from the list" 1540 | 1541 | $frmMain.Controls.Add($groupBox2) 1542 | #endregion groupBox2 1543 | 1544 | #region cmbSkuList 1545 | $cmbSkuList.DataBindings.DefaultDataSourceUpdateMode = 0 1546 | $cmbSkuList.FormattingEnabled = $True 1547 | $System_Drawing_Point = New-Object System.Drawing.Point 1548 | $System_Drawing_Point.X = 25 1549 | $System_Drawing_Point.Y = 24 1550 | $cmbSkuList.Location = $System_Drawing_Point 1551 | $cmbSkuList.Name = "cmbSkuList" 1552 | $System_Drawing_Size = New-Object System.Drawing.Size 1553 | $System_Drawing_Size.Height = 25 1554 | $System_Drawing_Size.Width = 452 1555 | $cmbSkuList.Size = $System_Drawing_Size 1556 | $cmbSkuList.TabIndex = 2 1557 | 1558 | $groupBox2.Controls.Add($cmbSkuList) 1559 | #endregion cmbSkuList 1560 | 1561 | #region label1 1562 | $label1.DataBindings.DefaultDataSourceUpdateMode = 0 1563 | $System_Drawing_Point = New-Object System.Drawing.Point 1564 | $System_Drawing_Point.X = 23 1565 | $System_Drawing_Point.Y = 21 1566 | $label1.Location = $System_Drawing_Point 1567 | $label1.Name = "label1" 1568 | $System_Drawing_Size = New-Object System.Drawing.Size 1569 | $System_Drawing_Size.Height = 71 1570 | $System_Drawing_Size.Width = 464 1571 | $label1.Size = $System_Drawing_Size 1572 | $label1.TabIndex = 5 1573 | $label1.Text = $uiHeader 1574 | 1575 | $frmMain.Controls.Add($label1) 1576 | #endregion label1 1577 | 1578 | #region groupBox1 1579 | $groupBox1.DataBindings.DefaultDataSourceUpdateMode = 0 1580 | $System_Drawing_Point = New-Object System.Drawing.Point 1581 | $System_Drawing_Point.X = 10 1582 | $System_Drawing_Point.Y = 95 1583 | $groupBox1.Location = $System_Drawing_Point 1584 | $groupBox1.Name = "groupBox1" 1585 | $System_Drawing_Size = New-Object System.Drawing.Size 1586 | $System_Drawing_Size.Height = 68 1587 | $System_Drawing_Size.Width = 490 1588 | $groupBox1.Size = $System_Drawing_Size 1589 | $groupBox1.TabIndex = 4 1590 | $groupBox1.TabStop = $False 1591 | $groupBox1.Text = "1. Choose a source" 1592 | 1593 | $frmMain.Controls.Add($groupBox1) 1594 | #endregion groupBox1 1595 | 1596 | #region txtSourcePath 1597 | $txtSourcePath.DataBindings.DefaultDataSourceUpdateMode = 0 1598 | $System_Drawing_Point = New-Object System.Drawing.Point 1599 | $System_Drawing_Point.X = 25 1600 | $System_Drawing_Point.Y = 24 1601 | $txtSourcePath.Location = $System_Drawing_Point 1602 | $txtSourcePath.Name = "txtSourcePath" 1603 | $System_Drawing_Size = New-Object System.Drawing.Size 1604 | $System_Drawing_Size.Height = 25 1605 | $System_Drawing_Size.Width = 418 1606 | $txtSourcePath.Size = $System_Drawing_Size 1607 | $txtSourcePath.TabIndex = 0 1608 | 1609 | $groupBox1.Controls.Add($txtSourcePath) 1610 | #endregion txtSourcePath 1611 | 1612 | #region btnBrowseWim 1613 | $btnBrowseWim.DataBindings.DefaultDataSourceUpdateMode = 0 1614 | $System_Drawing_Point = New-Object System.Drawing.Point 1615 | $System_Drawing_Point.X = 449 1616 | $System_Drawing_Point.Y = 24 1617 | $btnBrowseWim.Location = $System_Drawing_Point 1618 | $btnBrowseWim.Name = "btnBrowseWim" 1619 | $System_Drawing_Size = New-Object System.Drawing.Size 1620 | $System_Drawing_Size.Height = 25 1621 | $System_Drawing_Size.Width = 28 1622 | $btnBrowseWim.Size = $System_Drawing_Size 1623 | $btnBrowseWim.TabIndex = 1 1624 | $btnBrowseWim.Text = "..." 1625 | $btnBrowseWim.UseVisualStyleBackColor = $True 1626 | $btnBrowseWim.add_Click($btnBrowseWim_OnClick) 1627 | 1628 | $groupBox1.Controls.Add($btnBrowseWim) 1629 | #endregion btnBrowseWim 1630 | 1631 | $openFileDialog1.FileName = "openFileDialog1" 1632 | $openFileDialog1.ShowHelp = $True 1633 | 1634 | #endregion Form Code 1635 | 1636 | # Save the initial state of the form 1637 | $InitialFormWindowState = $frmMain.WindowState 1638 | 1639 | # Init the OnLoad event to correct the initial state of the form 1640 | $frmMain.add_Load($OnLoadForm_StateCorrection) 1641 | 1642 | # Return the constructed form. 1643 | $ret = $frmMain.ShowDialog() 1644 | 1645 | if (!($ret -ilike "OK")) 1646 | { 1647 | throw "Form session has been cancelled." 1648 | } 1649 | 1650 | if ([string]::IsNullOrEmpty($SourcePath)) 1651 | { 1652 | throw "No source path specified." 1653 | } 1654 | 1655 | # VHD Format 1656 | $VHDFormat = $cmbVhdFormat.SelectedItem 1657 | 1658 | # VHD Size 1659 | $SizeBytes = Invoke-Expression "$($numVhdSize.Value)$($cmbVhdSizeUnit.SelectedItem)" 1660 | 1661 | # Working Directory 1662 | $WorkingDirectory = $txtWorkingDirectory.Text 1663 | 1664 | # VHDPath 1665 | if (![string]::IsNullOrEmpty($txtVhdName.Text)) 1666 | { 1667 | $VHDPath = "$($WorkingDirectory)\$($txtVhdName.Text)" 1668 | } 1669 | 1670 | # Edition 1671 | if (![string]::IsNullOrEmpty($cmbSkuList.SelectedItem)) 1672 | { 1673 | $Edition = $cmbSkuList.SelectedItem 1674 | } 1675 | 1676 | # Because we used ShowDialog, we need to manually dispose of the form. 1677 | # This probably won't make much of a difference, but let's free up all of the resources we can 1678 | # before we start the conversion process. 1679 | 1680 | $frmMain.Dispose() 1681 | } 1682 | 1683 | if ($VHDFormat -ilike "AUTO") 1684 | { 1685 | if ($DiskLayout -eq "BIOS") 1686 | { 1687 | $VHDFormat = "VHD" 1688 | } 1689 | else 1690 | { 1691 | $VHDFormat = "VHDX" 1692 | } 1693 | } 1694 | 1695 | # 1696 | # Choose smallest supported block size for dynamic VHD(X) 1697 | # 1698 | $BlockSizeBytes = 1MB 1699 | 1700 | # There's a difference between the maximum sizes for VHDs and VHDXs. Make sure we follow it. 1701 | if ("VHD" -ilike $VHDFormat) 1702 | { 1703 | if ($SizeBytes -gt $vhdMaxSize) 1704 | { 1705 | Write-W2VWarn "For the VHD file format, the maximum file size is ~2040GB. We're automatically setting the size to 2040GB for you." 1706 | $SizeBytes = 2040GB 1707 | } 1708 | 1709 | $BlockSizeBytes = 512KB 1710 | } 1711 | 1712 | # Check if -VHDPath and -WorkingDirectory were both specified. 1713 | if ((![String]::IsNullOrEmpty($VHDPath)) -and (![String]::IsNullOrEmpty($WorkingDirectory))) 1714 | { 1715 | if ($WorkingDirectory -ne $pwd) 1716 | { 1717 | # If the WorkingDirectory is anything besides $pwd, tell people that the WorkingDirectory is being ignored. 1718 | Write-W2VWarn "Specifying -VHDPath and -WorkingDirectory at the same time is contradictory." 1719 | Write-W2VWarn "Ignoring the WorkingDirectory specification." 1720 | $WorkingDirectory = Split-Path $VHDPath -Parent 1721 | } 1722 | } 1723 | 1724 | if ($VHDPath) 1725 | { 1726 | # Check to see if there's a conflict between the specified file extension and the VHDFormat being used. 1727 | $ext = ([IO.FileInfo]$VHDPath).Extension 1728 | 1729 | if (!($ext -ilike ".$($VHDFormat)")) 1730 | { 1731 | throw "There is a mismatch between the VHDPath file extension ($($ext.ToUpper())), and the VHDFormat (.$($VHDFormat)). Please ensure that these match and try again." 1732 | } 1733 | } 1734 | 1735 | # Create a temporary name for the VHD(x). We'll name it properly at the end of the script. 1736 | if ([String]::IsNullOrEmpty($VHDPath)) 1737 | { 1738 | $VHDPath = Join-Path $WorkingDirectory "$($sessionKey).$($VHDFormat.ToLower())" 1739 | } 1740 | else 1741 | { 1742 | # Since we can't do Resolve-Path against a file that doesn't exist, we need to get creative in determining 1743 | # the full path that the user specified (or meant to specify if they gave us a relative path). 1744 | # Check to see if the path has a root specified. If it doesn't, use the working directory. 1745 | if (![IO.Path]::IsPathRooted($VHDPath)) 1746 | { 1747 | $VHDPath = Join-Path $WorkingDirectory $VHDPath 1748 | } 1749 | 1750 | $vhdFinalName = Split-Path $VHDPath -Leaf 1751 | $VHDPath = Join-Path (Split-Path $VHDPath -Parent) "$($sessionKey).$($VHDFormat.ToLower())" 1752 | } 1753 | 1754 | Write-W2VTrace "Temporary $VHDFormat path is : $VHDPath" 1755 | 1756 | # If we're using an ISO, mount it and get the path to the WIM file. 1757 | if (([IO.FileInfo]$SourcePath).Extension -ilike ".ISO") 1758 | { 1759 | # If the ISO isn't local, copy it down so we don't have to worry about resource contention 1760 | # or about network latency. 1761 | if (Test-IsNetworkLocation $SourcePath) 1762 | { 1763 | Write-W2VInfo "Copying ISO $(Split-Path $SourcePath -Leaf) to temp folder..." 1764 | robocopy $(Split-Path $SourcePath -Parent) $TempDirectory $(Split-Path $SourcePath -Leaf) | Out-Null 1765 | $SourcePath = "$($TempDirectory)\$(Split-Path $SourcePath -Leaf)" 1766 | 1767 | $tempSource = $SourcePath 1768 | } 1769 | 1770 | $isoPath = (Resolve-Path $SourcePath).Path 1771 | 1772 | Write-W2VInfo "Opening ISO $(Split-Path $isoPath -Leaf)..." 1773 | $openIso = Mount-DiskImage -ImagePath $isoPath -StorageType ISO -PassThru 1774 | # Refresh the DiskImage object so we can get the real information about it. I assume this is a bug. 1775 | $openIso = Get-DiskImage -ImagePath $isoPath 1776 | $driveLetter = ($openIso | Get-Volume).DriveLetter 1777 | 1778 | $SourcePath = "$($driveLetter):\sources\install.wim" 1779 | 1780 | # Check to see if there's a WIM file we can muck about with. 1781 | Write-W2VInfo "Looking for $($SourcePath)..." 1782 | if (!(Test-Path $SourcePath)) 1783 | { 1784 | throw "The specified ISO does not appear to be valid Windows installation media." 1785 | } 1786 | } 1787 | 1788 | # Check to see if the WIM is local, or on a network location. If the latter, copy it locally. 1789 | if (Test-IsNetworkLocation $SourcePath) 1790 | { 1791 | Write-W2VInfo "Copying WIM $(Split-Path $SourcePath -Leaf) to temp folder..." 1792 | robocopy $(Split-Path $SourcePath -Parent) $TempDirectory $(Split-Path $SourcePath -Leaf) | Out-Null 1793 | $SourcePath = "$($TempDirectory)\$(Split-Path $SourcePath -Leaf)" 1794 | 1795 | $tempSource = $SourcePath 1796 | } 1797 | 1798 | $SourcePath = (Resolve-Path $SourcePath).Path 1799 | 1800 | #################################################################################################### 1801 | # QUERY WIM INFORMATION AND EXTRACT THE INDEX OF TARGETED IMAGE 1802 | #################################################################################################### 1803 | 1804 | Write-W2VInfo "Looking for the requested Windows image in the WIM file" 1805 | $WindowsImage = Get-WindowsImage -ImagePath $SourcePath 1806 | 1807 | if (-not $WindowsImage -or ($WindowsImage -is [System.Array])) 1808 | { 1809 | # 1810 | # WIM may have multiple images. Filter on Edition (can be index or name) and try to find a unique image 1811 | # 1812 | $EditionIndex = 0; 1813 | if ([Int32]::TryParse($Edition, [ref]$EditionIndex)) 1814 | { 1815 | $WindowsImage = Get-WindowsImage -ImagePath $SourcePath -Index $EditionIndex 1816 | } 1817 | else 1818 | { 1819 | $WindowsImage = Get-WindowsImage -ImagePath $SourcePath | Where-Object {$_.ImageName -ilike "*$($Edition)"} 1820 | } 1821 | 1822 | if (-not $WindowsImage) 1823 | { 1824 | throw "Requested windows Image was not found on the WIM file!" 1825 | } 1826 | if ($WindowsImage -is [System.Array]) 1827 | { 1828 | Write-W2VInfo "WIM file has the following $($WindowsImage.Count) images that match filter *$($Edition)" 1829 | Get-WindowsImage -ImagePath $SourcePath 1830 | 1831 | Write-W2VError "You must specify an Edition or SKU index, since the WIM has more than one image." 1832 | throw "There are more than one images that match ImageName filter *$($Edition)" 1833 | } 1834 | } 1835 | 1836 | $ImageIndex = $WindowsImage[0].ImageIndex 1837 | 1838 | # We're good. Open the WIM container. 1839 | # NOTE: this is only required because we want to get the XML-based meta-data at the end. Is there a better way? 1840 | # If we can get this information from DISM cmdlets, we can remove the openWim constructs 1841 | $openWim = New-Object WIM2VHD.WimFile $SourcePath 1842 | 1843 | $openImage = $openWim[[Int32]$ImageIndex] 1844 | 1845 | if ($null -eq $openImage) 1846 | { 1847 | Write-W2VError "The specified edition does not appear to exist in the specified WIM." 1848 | Write-W2VError "Valid edition names are:" 1849 | $openWim.Images | %{ Write-W2VError " $($_.ImageFlags)" } 1850 | throw 1851 | } 1852 | 1853 | Write-W2VInfo "Image $($openImage.ImageIndex) selected ($($openImage.ImageFlags))..." 1854 | 1855 | # Check to make sure that the image we're applying is Windows 7 or greater. 1856 | if ($openImage.ImageVersion -lt $lowestSupportedVersion) 1857 | { 1858 | if ($openImage.ImageVersion -eq "0.0.0.0") 1859 | { 1860 | Write-W2VWarn "The specified WIM does not encode the Windows version." 1861 | } 1862 | else 1863 | { 1864 | throw "Convert-WindowsImage only supports Windows 7 and Windows 8 WIM files. The specified image (version $($openImage.ImageVersion)) does not appear to contain one of those operating systems." 1865 | } 1866 | } 1867 | 1868 | if ($hyperVEnabled) 1869 | { 1870 | Write-W2VInfo "Creating sparse disk..." 1871 | $newVhd = New-VHD -Path $VHDPath -SizeBytes $SizeBytes -BlockSizeBytes $BlockSizeBytes -Dynamic 1872 | 1873 | Write-W2VInfo "Mounting $VHDFormat..." 1874 | $disk = $newVhd | Mount-VHD -PassThru | Get-Disk 1875 | } 1876 | else 1877 | { 1878 | <# 1879 | Create the VHD using the VirtDisk Win32 API. 1880 | So, why not use the New-VHD cmdlet here? 1881 | 1882 | New-VHD depends on the Hyper-V Cmdlets, which aren't installed by default. 1883 | Installing those cmdlets isn't a big deal, but they depend on the Hyper-V WMI 1884 | APIs, which in turn depend on Hyper-V. In order to prevent Convert-WindowsImage 1885 | from being dependent on Hyper-V (and thus, x64 systems only), we're using the 1886 | VirtDisk APIs directly. 1887 | #> 1888 | 1889 | Write-W2VInfo "Creating sparse disk..." 1890 | [WIM2VHD.VirtualHardDisk]::CreateSparseDisk( 1891 | $VHDFormat, 1892 | $VHDPath, 1893 | $SizeBytes, 1894 | $true 1895 | ) 1896 | 1897 | # Attach the VHD.\ 1898 | Write-W2VInfo "Attaching $VHDFormat..." 1899 | $disk = Mount-DiskImage -ImagePath $VHDPath -PassThru | Get-DiskImage | Get-Disk 1900 | } 1901 | 1902 | switch ($DiskLayout) 1903 | { 1904 | "BIOS" 1905 | { 1906 | Write-W2VInfo "Initializing disk..." 1907 | Initialize-Disk -Number $disk.Number -PartitionStyle MBR 1908 | 1909 | # 1910 | # Create the Windows/system partition 1911 | # 1912 | Write-W2VInfo "Creating single partition..." 1913 | $systemPartition = New-Partition -DiskNumber $disk.Number -UseMaximumSize -MbrType IFS -IsActive 1914 | $windowsPartition = $systemPartition 1915 | 1916 | Write-W2VInfo "Formatting windows volume..." 1917 | $systemVolume = Format-Volume -Partition $systemPartition -FileSystem NTFS -Force -Confirm:$false 1918 | $windowsVolume = $systemVolume 1919 | } 1920 | 1921 | "UEFI" 1922 | { 1923 | Write-W2VInfo "Initializing disk..." 1924 | Initialize-Disk -Number $disk.Number -PartitionStyle GPT 1925 | 1926 | if ((Get-WindowsBuildNumber) -ge 10240) 1927 | { 1928 | # 1929 | # Create the system partition. Create a data partition so we can format it, then change to ESP 1930 | # 1931 | Write-W2VInfo "Creating EFI system partition..." 1932 | $systemPartition = New-Partition -DiskNumber $disk.Number -Size 200MB -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' 1933 | 1934 | Write-W2VInfo "Formatting system volume..." 1935 | $systemVolume = Format-Volume -Partition $systemPartition -FileSystem FAT32 -Force -Confirm:$false 1936 | 1937 | Write-W2VInfo "Setting system partition as ESP..." 1938 | $systemPartition | Set-Partition -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' 1939 | $systemPartition | Add-PartitionAccessPath -AssignDriveLetter 1940 | } 1941 | else 1942 | { 1943 | # 1944 | # Create the system partition 1945 | # 1946 | Write-W2VInfo "Creating EFI system partition (ESP)..." 1947 | $systemPartition = New-Partition -DiskNumber $disk.Number -Size 200MB -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' -AssignDriveLetter 1948 | 1949 | Write-W2VInfo "Formatting ESP..." 1950 | $formatArgs = @( 1951 | "$($systemPartition.DriveLetter):", # Partition drive letter 1952 | "/FS:FAT32", # File system 1953 | "/Q", # Quick format 1954 | "/Y" # Suppress prompt 1955 | ) 1956 | 1957 | Run-Executable -Executable format -Arguments $formatArgs 1958 | } 1959 | 1960 | # 1961 | # Create the reserved partition 1962 | # 1963 | Write-W2VInfo "Creating MSR partition..." 1964 | $reservedPartition = New-Partition -DiskNumber $disk.Number -Size 128MB -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}' 1965 | 1966 | # 1967 | # Create the Windows partition 1968 | # 1969 | Write-W2VInfo "Creating windows partition..." 1970 | $windowsPartition = New-Partition -DiskNumber $disk.Number -UseMaximumSize -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' 1971 | 1972 | Write-W2VInfo "Formatting windows volume..." 1973 | $windowsVolume = Format-Volume -Partition $windowsPartition -FileSystem NTFS -Force -Confirm:$false 1974 | } 1975 | 1976 | "WindowsToGo" 1977 | { 1978 | Write-W2VInfo "Initializing disk..." 1979 | Initialize-Disk -Number $disk.Number -PartitionStyle MBR 1980 | 1981 | # 1982 | # Create the system partition 1983 | # 1984 | Write-W2VInfo "Creating system partition..." 1985 | $systemPartition = New-Partition -DiskNumber $disk.Number -Size 350MB -MbrType FAT32 -IsActive 1986 | 1987 | Write-W2VInfo "Formatting system volume..." 1988 | $systemVolume = Format-Volume -Partition $systemPartition -FileSystem FAT32 -Force -Confirm:$false 1989 | 1990 | # 1991 | # Create the Windows partition 1992 | # 1993 | Write-W2VInfo "Creating windows partition..." 1994 | $windowsPartition = New-Partition -DiskNumber $disk.Number -UseMaximumSize -MbrType IFS 1995 | 1996 | Write-W2VInfo "Formatting windows volume..." 1997 | $windowsVolume = Format-Volume -Partition $windowsPartition -FileSystem NTFS -Force -Confirm:$false 1998 | } 1999 | } 2000 | 2001 | # 2002 | # Assign drive letter to Windows partition. This is required for bcdboot 2003 | # 2004 | 2005 | $attempts = 1 2006 | $assigned = $false 2007 | 2008 | do 2009 | { 2010 | $windowsPartition | Add-PartitionAccessPath -AssignDriveLetter 2011 | $windowsPartition = $windowsPartition | Get-Partition 2012 | if($windowsPartition.DriveLetter -ne 0) 2013 | { 2014 | $assigned = $true 2015 | } 2016 | else 2017 | { 2018 | #sleep for up to 10 seconds and retry 2019 | Get-Random -Minimum 1 -Maximum 10 | Start-Sleep 2020 | 2021 | $attempts++ 2022 | } 2023 | } 2024 | while ($attempts -le 100 -and -not($assigned)) 2025 | 2026 | if (-not($assigned)) 2027 | { 2028 | throw "Unable to get Partition after retry" 2029 | } 2030 | 2031 | $windowsDrive = $(Get-Partition -Volume $windowsVolume).AccessPaths[0].substring(0,2) 2032 | Write-W2VInfo "Windows path ($windowsDrive) has been assigned." 2033 | Write-W2VInfo "Windows path ($windowsDrive) took $attempts attempts to be assigned." 2034 | 2035 | # 2036 | # Refresh access paths (we have now formatted the volume) 2037 | # 2038 | $systemPartition = $systemPartition | Get-Partition 2039 | $systemDrive = $systemPartition.AccessPaths[0].trimend("\").replace("\?", "??") 2040 | Write-W2VInfo "System volume location: $systemDrive" 2041 | 2042 | #################################################################################################### 2043 | # APPLY IMAGE FROM WIM TO THE NEW VHD 2044 | #################################################################################################### 2045 | 2046 | Write-W2VInfo "Applying image to $VHDFormat. This could take a while..." 2047 | if ((Get-Command Expand-WindowsImage -ErrorAction SilentlyContinue) -and ((-not $ApplyEA) -and ([string]::IsNullOrEmpty($DismPath)))) 2048 | { 2049 | Expand-WindowsImage -ApplyPath $windowsDrive -ImagePath $SourcePath -Index $ImageIndex -LogPath "$($logFolder)\DismLogs.log" | Out-Null 2050 | } 2051 | else 2052 | { 2053 | if (![string]::IsNullOrEmpty($DismPath)) 2054 | { 2055 | $dismPath = $DismPath 2056 | } 2057 | else 2058 | { 2059 | $dismPath = $(Join-Path (get-item env:\windir).value "system32\dism.exe") 2060 | } 2061 | 2062 | $applyImage = "/Apply-Image" 2063 | if ($ApplyEA) 2064 | { 2065 | $applyImage = $applyImage + " /EA" 2066 | } 2067 | 2068 | $dismArgs = @("$applyImage /ImageFile:`"$SourcePath`" /Index:$ImageIndex /ApplyDir:$windowsDrive /LogPath:`"$($logFolder)\DismLogs.log`"") 2069 | Write-W2VInfo "Applying image: $dismPath $dismArgs" 2070 | $process = Start-Process -Passthru -Wait -NoNewWindow -FilePath $dismPath ` 2071 | -ArgumentList $dismArgs ` 2072 | 2073 | if ($process.ExitCode -ne 0) 2074 | { 2075 | throw "Image Apply failed! See DismImageApply logs for details" 2076 | } 2077 | } 2078 | Write-W2VInfo "Image was applied successfully. " 2079 | 2080 | # 2081 | # Here we copy in the unattend file (if specified by the command line) 2082 | # 2083 | if (![string]::IsNullOrEmpty($UnattendPath)) 2084 | { 2085 | Write-W2VInfo "Applying unattend file ($(Split-Path $UnattendPath -Leaf))..." 2086 | Copy-Item -Path $UnattendPath -Destination (Join-Path $windowsDrive "unattend.xml") -Force 2087 | } 2088 | 2089 | if (![string]::IsNullOrEmpty($MergeFolderPath)) 2090 | { 2091 | Write-W2VInfo "Applying merge folder ($MergeFolderPath)..." 2092 | Copy-Item -Recurse -Path (Join-Path $MergeFolderPath "*") -Destination $windowsDrive -Force #added to handle merge folders 2093 | } 2094 | 2095 | if (($openImage.ImageArchitecture -ne "ARM") -and # No virtualization platform for ARM images 2096 | ($openImage.ImageArchitecture -ne "ARM64") -and # No virtualization platform for ARM64 images 2097 | ($BCDinVHD -ne "NativeBoot")) # User asked for a non-bootable image 2098 | { 2099 | if (Test-Path "$($systemDrive)\boot\bcd") 2100 | { 2101 | Write-W2VInfo "Image already has BIOS BCD store..." 2102 | } 2103 | elseif (Test-Path "$($systemDrive)\efi\microsoft\boot\bcd") 2104 | { 2105 | Write-W2VInfo "Image already has EFI BCD store..." 2106 | } 2107 | else 2108 | { 2109 | Write-W2VInfo "Making image bootable..." 2110 | $bcdBootArgs = @( 2111 | "$($windowsDrive)\Windows", # Path to the \Windows on the VHD 2112 | "/s $systemDrive", # Specifies the volume letter of the drive to create the \BOOT folder on. 2113 | "/v" # Enabled verbose logging. 2114 | ) 2115 | 2116 | switch ($DiskLayout) 2117 | { 2118 | "BIOS" 2119 | { 2120 | $bcdBootArgs += "/f BIOS" # Specifies the firmware type of the target system partition 2121 | } 2122 | 2123 | "UEFI" 2124 | { 2125 | $bcdBootArgs += "/f UEFI" # Specifies the firmware type of the target system partition 2126 | } 2127 | 2128 | "WindowsToGo" 2129 | { 2130 | # Create entries for both UEFI and BIOS if possible 2131 | if (Test-Path "$($windowsDrive)\Windows\boot\EFI\bootmgfw.efi") 2132 | { 2133 | $bcdBootArgs += "/f ALL" 2134 | } 2135 | } 2136 | } 2137 | 2138 | Run-Executable -Executable $BCDBoot -Arguments $bcdBootArgs 2139 | 2140 | # The following is added to mitigate the VMM diff disk handling 2141 | # We're going to change from MBRBootOption to LocateBootOption. 2142 | 2143 | if ($DiskLayout -eq "BIOS") 2144 | { 2145 | Write-W2VInfo "Fixing the Device ID in the BCD store on $($VHDFormat)..." 2146 | Run-Executable -Executable "BCDEDIT.EXE" -Arguments ( 2147 | "/store $($systemDrive)\boot\bcd", 2148 | "/set `{bootmgr`} device locate" 2149 | ) 2150 | Run-Executable -Executable "BCDEDIT.EXE" -Arguments ( 2151 | "/store $($systemDrive)\boot\bcd", 2152 | "/set `{default`} device locate" 2153 | ) 2154 | Run-Executable -Executable "BCDEDIT.EXE" -Arguments ( 2155 | "/store $($systemDrive)\boot\bcd", 2156 | "/set `{default`} osdevice locate" 2157 | ) 2158 | } 2159 | } 2160 | 2161 | Write-W2VInfo "Drive is bootable. Cleaning up..." 2162 | 2163 | # Are we turning the debugger on? 2164 | if ($EnableDebugger -inotlike "None") 2165 | { 2166 | $bcdEditArgs = $null; 2167 | 2168 | # Configure the specified debugging transport and other settings. 2169 | switch ($EnableDebugger) 2170 | { 2171 | "Serial" 2172 | { 2173 | $bcdEditArgs = @( 2174 | "/dbgsettings SERIAL", 2175 | "DEBUGPORT:$($ComPort.Value)", 2176 | "BAUDRATE:$($BaudRate.Value)" 2177 | ) 2178 | } 2179 | 2180 | "1394" 2181 | { 2182 | $bcdEditArgs = @( 2183 | "/dbgsettings 1394", 2184 | "CHANNEL:$($Channel.Value)" 2185 | ) 2186 | } 2187 | 2188 | "USB" 2189 | { 2190 | $bcdEditArgs = @( 2191 | "/dbgsettings USB", 2192 | "TARGETNAME:$($Target.Value)" 2193 | ) 2194 | } 2195 | 2196 | "Local" 2197 | { 2198 | $bcdEditArgs = @( 2199 | "/dbgsettings LOCAL" 2200 | ) 2201 | } 2202 | 2203 | "Network" 2204 | { 2205 | $bcdEditArgs = @( 2206 | "/dbgsettings NET", 2207 | "HOSTIP:$($IP.Value)", 2208 | "PORT:$($Port.Value)", 2209 | "KEY:$($Key.Value)" 2210 | ) 2211 | } 2212 | } 2213 | 2214 | $bcdStores = @( 2215 | "$($systemDrive)\boot\bcd", 2216 | "$($systemDrive)\efi\microsoft\boot\bcd" 2217 | ) 2218 | 2219 | foreach ($bcdStore in $bcdStores) 2220 | { 2221 | if (Test-Path $bcdStore) 2222 | { 2223 | Write-W2VInfo "Turning kernel debugging on in the $($VHDFormat) for $($bcdStore)..." 2224 | Run-Executable -Executable "BCDEDIT.EXE" -Arguments ( 2225 | "/store $($bcdStore)", 2226 | "/set `{default`} debug on" 2227 | ) 2228 | 2229 | $bcdEditArguments = @("/store $($bcdStore)") + $bcdEditArgs 2230 | 2231 | Run-Executable -Executable "BCDEDIT.EXE" -Arguments $bcdEditArguments 2232 | } 2233 | } 2234 | } 2235 | } 2236 | else 2237 | { 2238 | # Don't bother to check on debugging. We can't boot WoA VHDs in VMs, and 2239 | # if we're native booting, the changes need to be made to the BCD store on the 2240 | # physical computer's boot volume. 2241 | 2242 | Write-W2VInfo "Image applied. It is not bootable." 2243 | } 2244 | 2245 | if ($RemoteDesktopEnable -or (-not $ExpandOnNativeBoot)) 2246 | { 2247 | $hive = Mount-RegistryHive -Hive (Join-Path $windowsDrive "Windows\System32\Config\System") 2248 | 2249 | if ($RemoteDesktopEnable) 2250 | { 2251 | Write-W2VInfo -text "Enabling Remote Desktop" 2252 | Set-ItemProperty -Path "HKLM:\$($hive)\ControlSet001\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0 2253 | } 2254 | 2255 | if (-not $ExpandOnNativeBoot) 2256 | { 2257 | Write-W2VInfo -text "Disabling automatic $VHDFormat expansion for Native Boot" 2258 | Set-ItemProperty -Path "HKLM:\$($hive)\ControlSet001\Services\FsDepends\Parameters" -Name "VirtualDiskExpandOnMount" -Value 4 2259 | } 2260 | 2261 | Dismount-RegistryHive -HiveMountPoint $hive 2262 | } 2263 | 2264 | if ($Driver) 2265 | { 2266 | Write-W2VInfo -text "Adding Windows Drivers to the Image" 2267 | $Driver | ForEach-Object -Process { 2268 | Write-W2VInfo -text "Driver path: $PSItem" 2269 | Add-WindowsDriver -Path $windowsDrive -Recurse -Driver $PSItem -Verbose | Out-Null 2270 | } 2271 | } 2272 | 2273 | If ($Feature) 2274 | { 2275 | Write-W2VInfo -text "Installing Windows Feature(s) $Feature to the Image" 2276 | $FeatureSourcePath = Join-Path -Path "$($driveLetter):" -ChildPath "sources\sxs" 2277 | Write-W2VInfo -text "From $FeatureSourcePath" 2278 | Enable-WindowsOptionalFeature -FeatureName $Feature -Source $FeatureSourcePath -Path $windowsDrive -All | Out-Null 2279 | } 2280 | 2281 | if ($Package) 2282 | { 2283 | Write-W2VInfo -text "Adding Windows Packages to the Image" 2284 | 2285 | $Package | ForEach-Object -Process { 2286 | Write-W2VInfo -text "Package path: $PSItem" 2287 | Add-WindowsPackage -Path $windowsDrive -PackagePath $PSItem | Out-Null 2288 | } 2289 | } 2290 | 2291 | # 2292 | # Remove system partition access path, if necessary 2293 | # 2294 | if ($DiskLayout -eq "UEFI") 2295 | { 2296 | $systemPartition | Remove-PartitionAccessPath -AccessPath $systemPartition.AccessPaths[0] 2297 | } 2298 | 2299 | if ([String]::IsNullOrEmpty($vhdFinalName)) 2300 | { 2301 | # We need to generate a file name. 2302 | Write-W2VInfo "Generating name for $($VHDFormat)..." 2303 | $hive = Mount-RegistryHive -Hive (Join-Path $windowsDrive "Windows\System32\Config\Software") 2304 | 2305 | $buildLabEx = (Get-ItemProperty "HKLM:\$($hive)\Microsoft\Windows NT\CurrentVersion").BuildLabEx 2306 | $installType = (Get-ItemProperty "HKLM:\$($hive)\Microsoft\Windows NT\CurrentVersion").InstallationType 2307 | $editionId = (Get-ItemProperty "HKLM:\$($hive)\Microsoft\Windows NT\CurrentVersion").EditionID 2308 | $skuFamily = $null 2309 | 2310 | Dismount-RegistryHive -HiveMountPoint $hive 2311 | 2312 | # Is this ServerCore? 2313 | # Since we're only doing this string comparison against the InstallType key, we won't get 2314 | # false positives with the Core SKU. 2315 | if ($installType.ToUpper().Contains("CORE")) 2316 | { 2317 | $editionId += "Core" 2318 | } 2319 | 2320 | # What type of SKU are we? 2321 | if ($installType.ToUpper().Contains("SERVER")) 2322 | { 2323 | $skuFamily = "Server" 2324 | } 2325 | elseif ($installType.ToUpper().Contains("CLIENT")) 2326 | { 2327 | $skuFamily = "Client" 2328 | } 2329 | else 2330 | { 2331 | $skuFamily = "Unknown" 2332 | } 2333 | 2334 | # 2335 | # ISSUE - do we want VL here? 2336 | # 2337 | $vhdFinalName = "$($buildLabEx)_$($skuFamily)_$($editionId)_$($openImage.ImageDefaultLanguage).$($VHDFormat.ToLower())" 2338 | Write-W2VTrace "$VHDFormat final name is : $vhdFinalName" 2339 | } 2340 | 2341 | if ($hyperVEnabled) 2342 | { 2343 | Write-W2VInfo "Dismounting $VHDFormat..." 2344 | Dismount-VHD -Path $VHDPath 2345 | } 2346 | else 2347 | { 2348 | Write-W2VInfo "Closing $VHDFormat..." 2349 | Dismount-DiskImage -ImagePath $VHDPath 2350 | } 2351 | 2352 | $vhdFinalPath = Join-Path (Split-Path $VHDPath -Parent) $vhdFinalName 2353 | Write-W2VTrace "$VHDFormat final path is : $vhdFinalPath" 2354 | 2355 | if (Test-Path $vhdFinalPath) 2356 | { 2357 | Write-W2VInfo "Deleting pre-existing $VHDFormat : $(Split-Path $vhdFinalPath -Leaf)..." 2358 | Remove-Item -Path $vhdFinalPath -Force 2359 | } 2360 | 2361 | Write-W2VTrace -Text "Renaming $VHDFormat at $VHDPath to $vhdFinalName" 2362 | Rename-Item -Path (Resolve-Path $VHDPath).Path -NewName $vhdFinalName -Force 2363 | $vhd += Get-DiskImage -ImagePath $vhdFinalPath 2364 | 2365 | $vhdFinalName = $null 2366 | } 2367 | catch 2368 | { 2369 | Write-W2VError $_ 2370 | Write-W2VInfo "Log folder is $logFolder" 2371 | } 2372 | finally 2373 | { 2374 | # If we still have a WIM image open, close it. 2375 | if ($openWim -ne $null) 2376 | { 2377 | Write-W2VInfo "Closing Windows image..." 2378 | $openWim.Close() 2379 | } 2380 | 2381 | # If we still have a registry hive mounted, dismount it. 2382 | if ($mountedHive -ne $null) 2383 | { 2384 | Write-W2VInfo "Closing registry hive..." 2385 | Dismount-RegistryHive -HiveMountPoint $mountedHive 2386 | } 2387 | 2388 | # If VHD is mounted, unmount it 2389 | if (Test-Path $VHDPath) 2390 | { 2391 | if ($hyperVEnabled) 2392 | { 2393 | if ((Get-VHD -Path $VHDPath).Attached) 2394 | { 2395 | Dismount-VHD -Path $VHDPath 2396 | } 2397 | } 2398 | else 2399 | { 2400 | Dismount-DiskImage -ImagePath $VHDPath 2401 | } 2402 | } 2403 | 2404 | # If we still have an ISO open, close it. 2405 | if ($openIso -ne $null) 2406 | { 2407 | Write-W2VInfo "Closing ISO..." 2408 | Dismount-DiskImage $ISOPath 2409 | } 2410 | 2411 | if (-not $CacheSource) 2412 | { 2413 | if ($tempSource -and (Test-Path $tempSource)) 2414 | { 2415 | Remove-Item -Path $tempSource -Force 2416 | } 2417 | } 2418 | 2419 | # Close out the transcript and tell the user we're done. 2420 | Write-W2VInfo "Done." 2421 | if ($transcripting) 2422 | { 2423 | $null = Stop-Transcript 2424 | } 2425 | } 2426 | } 2427 | 2428 | End 2429 | { 2430 | if ($Passthru) 2431 | { 2432 | return $vhd 2433 | } 2434 | } 2435 | #endregion Code 2436 | 2437 | } 2438 | 2439 | 2440 | function 2441 | Add-WindowsImageTypes 2442 | { 2443 | $code = @" 2444 | using System; 2445 | using System.Collections.Generic; 2446 | using System.Collections.ObjectModel; 2447 | using System.ComponentModel; 2448 | using System.Globalization; 2449 | using System.IO; 2450 | using System.Linq; 2451 | using System.Runtime.InteropServices; 2452 | using System.Security; 2453 | using System.Text; 2454 | using System.Text.RegularExpressions; 2455 | using System.Threading; 2456 | using System.Xml.Linq; 2457 | using System.Xml.XPath; 2458 | using Microsoft.Win32.SafeHandles; 2459 | 2460 | namespace WIM2VHD 2461 | { 2462 | 2463 | /// 2464 | /// P/Invoke methods and associated enums, flags, and structs. 2465 | /// 2466 | public class 2467 | NativeMethods 2468 | { 2469 | 2470 | #region Delegates and Callbacks 2471 | #region WIMGAPI 2472 | 2473 | /// 2474 | ///User-defined function used with the RegisterMessageCallback or UnregisterMessageCallback function. 2475 | /// 2476 | ///Specifies the message being sent. 2477 | ///Specifies additional message information. The contents of this parameter depend on the value of the 2478 | ///MessageId parameter. 2479 | ///Specifies additional message information. The contents of this parameter depend on the value of the 2480 | ///MessageId parameter. 2481 | ///Specifies the user-defined value passed to RegisterCallback. 2482 | /// 2483 | ///To indicate success and to enable other subscribers to process the message return WIM_MSG_SUCCESS. 2484 | ///To prevent other subscribers from receiving the message, return WIM_MSG_DONE. 2485 | ///To cancel an image apply or capture, return WIM_MSG_ABORT_IMAGE when handling the WIM_MSG_PROCESS message. 2486 | /// 2487 | public delegate uint 2488 | WimMessageCallback( 2489 | uint MessageId, 2490 | IntPtr wParam, 2491 | IntPtr lParam, 2492 | IntPtr UserData 2493 | ); 2494 | 2495 | public static void 2496 | RegisterMessageCallback( 2497 | WimFileHandle hWim, 2498 | WimMessageCallback callback) 2499 | { 2500 | 2501 | uint _callback = NativeMethods.WimRegisterMessageCallback(hWim, callback, IntPtr.Zero); 2502 | int rc = Marshal.GetLastWin32Error(); 2503 | if (0 != rc) 2504 | { 2505 | // Throw an exception if something bad happened on the Win32 end. 2506 | throw 2507 | new InvalidOperationException( 2508 | string.Format( 2509 | CultureInfo.CurrentCulture, 2510 | "Unable to register message callback." 2511 | )); 2512 | } 2513 | } 2514 | 2515 | public static void 2516 | UnregisterMessageCallback( 2517 | WimFileHandle hWim, 2518 | WimMessageCallback registeredCallback) 2519 | { 2520 | 2521 | bool status = NativeMethods.WimUnregisterMessageCallback(hWim, registeredCallback); 2522 | int rc = Marshal.GetLastWin32Error(); 2523 | if (!status) 2524 | { 2525 | throw 2526 | new InvalidOperationException( 2527 | string.Format( 2528 | CultureInfo.CurrentCulture, 2529 | "Unable to unregister message callback." 2530 | )); 2531 | } 2532 | } 2533 | 2534 | #endregion WIMGAPI 2535 | #endregion Delegates and Callbacks 2536 | 2537 | #region Constants 2538 | 2539 | #region VDiskInterop 2540 | 2541 | /// 2542 | /// The default depth in a VHD parent chain that this library will search through. 2543 | /// If you want to go more than one disk deep into the parent chain, provide a different value. 2544 | /// 2545 | public const uint OPEN_VIRTUAL_DISK_RW_DEFAULT_DEPTH = 0x00000001; 2546 | 2547 | public const uint DEFAULT_BLOCK_SIZE = 0x00080000; 2548 | public const uint DISK_SECTOR_SIZE = 0x00000200; 2549 | 2550 | internal const uint ERROR_VIRTDISK_NOT_VIRTUAL_DISK = 0xC03A0015; 2551 | internal const uint ERROR_NOT_FOUND = 0x00000490; 2552 | internal const uint ERROR_IO_PENDING = 0x000003E5; 2553 | internal const uint ERROR_INSUFFICIENT_BUFFER = 0x0000007A; 2554 | internal const uint ERROR_ERROR_DEV_NOT_EXIST = 0x00000037; 2555 | internal const uint ERROR_BAD_COMMAND = 0x00000016; 2556 | internal const uint ERROR_SUCCESS = 0x00000000; 2557 | 2558 | public const uint GENERIC_READ = 0x80000000; 2559 | public const uint GENERIC_WRITE = 0x40000000; 2560 | public const short FILE_ATTRIBUTE_NORMAL = 0x00000080; 2561 | public const uint CREATE_NEW = 0x00000001; 2562 | public const uint CREATE_ALWAYS = 0x00000002; 2563 | public const uint OPEN_EXISTING = 0x00000003; 2564 | public const short INVALID_HANDLE_VALUE = -1; 2565 | 2566 | internal static Guid VirtualStorageTypeVendorUnknown = new Guid("00000000-0000-0000-0000-000000000000"); 2567 | internal static Guid VirtualStorageTypeVendorMicrosoft = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B"); 2568 | 2569 | #endregion VDiskInterop 2570 | 2571 | #region WIMGAPI 2572 | 2573 | public const uint WIM_FLAG_VERIFY = 0x00000002; 2574 | public const uint WIM_FLAG_INDEX = 0x00000004; 2575 | 2576 | public const uint WM_APP = 0x00008000; 2577 | 2578 | #endregion WIMGAPI 2579 | 2580 | #endregion Constants 2581 | 2582 | #region Enums and Flags 2583 | 2584 | #region VDiskInterop 2585 | 2586 | /// 2587 | /// Indicates the version of the virtual disk to create. 2588 | /// 2589 | public enum CreateVirtualDiskVersion : int 2590 | { 2591 | VersionUnspecified = 0x00000000, 2592 | Version1 = 0x00000001, 2593 | Version2 = 0x00000002 2594 | } 2595 | 2596 | public enum OpenVirtualDiskVersion : int 2597 | { 2598 | VersionUnspecified = 0x00000000, 2599 | Version1 = 0x00000001, 2600 | Version2 = 0x00000002 2601 | } 2602 | 2603 | /// 2604 | /// Contains the version of the virtual hard disk (VHD) ATTACH_VIRTUAL_DISK_PARAMETERS structure to use in calls to VHD functions. 2605 | /// 2606 | public enum AttachVirtualDiskVersion : int 2607 | { 2608 | VersionUnspecified = 0x00000000, 2609 | Version1 = 0x00000001, 2610 | Version2 = 0x00000002 2611 | } 2612 | 2613 | public enum CompactVirtualDiskVersion : int 2614 | { 2615 | VersionUnspecified = 0x00000000, 2616 | Version1 = 0x00000001 2617 | } 2618 | 2619 | /// 2620 | /// Contains the type and provider (vendor) of the virtual storage device. 2621 | /// 2622 | public enum VirtualStorageDeviceType : int 2623 | { 2624 | /// 2625 | /// The storage type is unknown or not valid. 2626 | /// 2627 | Unknown = 0x00000000, 2628 | /// 2629 | /// For internal use only. This type is not supported. 2630 | /// 2631 | ISO = 0x00000001, 2632 | /// 2633 | /// Virtual Hard Disk device type. 2634 | /// 2635 | VHD = 0x00000002, 2636 | /// 2637 | /// Virtual Hard Disk v2 device type. 2638 | /// 2639 | VHDX = 0x00000003 2640 | } 2641 | 2642 | /// 2643 | /// Contains virtual hard disk (VHD) open request flags. 2644 | /// 2645 | [Flags] 2646 | public enum OpenVirtualDiskFlags 2647 | { 2648 | /// 2649 | /// No flags. Use system defaults. 2650 | /// 2651 | None = 0x00000000, 2652 | /// 2653 | /// Open the VHD file (backing store) without opening any differencing-chain parents. Used to correct broken parent links. 2654 | /// 2655 | NoParents = 0x00000001, 2656 | /// 2657 | /// Reserved. 2658 | /// 2659 | BlankFile = 0x00000002, 2660 | /// 2661 | /// Reserved. 2662 | /// 2663 | BootDrive = 0x00000004, 2664 | } 2665 | 2666 | /// 2667 | /// Contains the bit mask for specifying access rights to a virtual hard disk (VHD). 2668 | /// 2669 | [Flags] 2670 | public enum VirtualDiskAccessMask 2671 | { 2672 | /// 2673 | /// Only Version2 of OpenVirtualDisk API accepts this parameter 2674 | /// 2675 | None = 0x00000000, 2676 | /// 2677 | /// Open the virtual disk for read-only attach access. The caller must have READ access to the virtual disk image file. 2678 | /// 2679 | /// 2680 | /// If used in a request to open a virtual disk that is already open, the other handles must be limited to either 2681 | /// VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail. 2682 | /// 2683 | AttachReadOnly = 0x00010000, 2684 | /// 2685 | /// Open the virtual disk for read-write attaching access. The caller must have (READ | WRITE) access to the virtual disk image file. 2686 | /// 2687 | /// 2688 | /// If used in a request to open a virtual disk that is already open, the other handles must be limited to either 2689 | /// VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail. 2690 | /// If the virtual disk is part of a differencing chain, the disk for this request cannot be less than the readWriteDepth specified 2691 | /// during the prior open request for that differencing chain. 2692 | /// 2693 | AttachReadWrite = 0x00020000, 2694 | /// 2695 | /// Open the virtual disk to allow detaching of an attached virtual disk. The caller must have 2696 | /// (FILE_READ_ATTRIBUTES | FILE_READ_DATA) access to the virtual disk image file. 2697 | /// 2698 | Detach = 0x00040000, 2699 | /// 2700 | /// Information retrieval access to the virtual disk. The caller must have READ access to the virtual disk image file. 2701 | /// 2702 | GetInfo = 0x00080000, 2703 | /// 2704 | /// Virtual disk creation access. 2705 | /// 2706 | Create = 0x00100000, 2707 | /// 2708 | /// Open the virtual disk to perform offline meta-operations. The caller must have (READ | WRITE) access to the virtual 2709 | /// disk image file, up to readWriteDepth if working with a differencing chain. 2710 | /// 2711 | /// 2712 | /// If the virtual disk is part of a differencing chain, the backing store (host volume) is opened in RW exclusive mode up to readWriteDepth. 2713 | /// 2714 | MetaOperations = 0x00200000, 2715 | /// 2716 | /// Reserved. 2717 | /// 2718 | Read = 0x000D0000, 2719 | /// 2720 | /// Allows unrestricted access to the virtual disk. The caller must have unrestricted access rights to the virtual disk image file. 2721 | /// 2722 | All = 0x003F0000, 2723 | /// 2724 | /// Reserved. 2725 | /// 2726 | Writable = 0x00320000 2727 | } 2728 | 2729 | /// 2730 | /// Contains virtual hard disk (VHD) creation flags. 2731 | /// 2732 | [Flags] 2733 | public enum CreateVirtualDiskFlags 2734 | { 2735 | /// 2736 | /// Contains virtual hard disk (VHD) creation flags. 2737 | /// 2738 | None = 0x00000000, 2739 | /// 2740 | /// Pre-allocate all physical space necessary for the size of the virtual disk. 2741 | /// 2742 | /// 2743 | /// The CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION flag is used for the creation of a fixed VHD. 2744 | /// 2745 | FullPhysicalAllocation = 0x00000001 2746 | } 2747 | 2748 | /// 2749 | /// Contains virtual disk attach request flags. 2750 | /// 2751 | [Flags] 2752 | public enum AttachVirtualDiskFlags 2753 | { 2754 | /// 2755 | /// No flags. Use system defaults. 2756 | /// 2757 | None = 0x00000000, 2758 | /// 2759 | /// Attach the virtual disk as read-only. 2760 | /// 2761 | ReadOnly = 0x00000001, 2762 | /// 2763 | /// No drive letters are assigned to the disk's volumes. 2764 | /// 2765 | /// Oddly enough, this doesn't apply to NTFS mount points. 2766 | NoDriveLetter = 0x00000002, 2767 | /// 2768 | /// Will decouple the virtual disk lifetime from that of the VirtualDiskHandle. 2769 | /// The virtual disk will be attached until the Detach() function is called, even if all open handles to the virtual disk are closed. 2770 | /// 2771 | PermanentLifetime = 0x00000004, 2772 | /// 2773 | /// Reserved. 2774 | /// 2775 | NoLocalHost = 0x00000008 2776 | } 2777 | 2778 | [Flags] 2779 | public enum DetachVirtualDiskFlag 2780 | { 2781 | None = 0x00000000 2782 | } 2783 | 2784 | [Flags] 2785 | public enum CompactVirtualDiskFlags 2786 | { 2787 | None = 0x00000000, 2788 | NoZeroScan = 0x00000001, 2789 | NoBlockMoves = 0x00000002 2790 | } 2791 | 2792 | #endregion VDiskInterop 2793 | 2794 | #region WIMGAPI 2795 | 2796 | [FlagsAttribute] 2797 | internal enum 2798 | WimCreateFileDesiredAccess : uint 2799 | { 2800 | WimQuery = 0x00000000, 2801 | WimGenericRead = 0x80000000 2802 | } 2803 | 2804 | public enum WimMessage : uint 2805 | { 2806 | WIM_MSG = WM_APP + 0x1476, 2807 | WIM_MSG_TEXT, 2808 | /// 2809 | ///Indicates an update in the progress of an image application. 2810 | /// 2811 | WIM_MSG_PROGRESS, 2812 | /// 2813 | ///Enables the caller to prevent a file or a directory from being captured or applied. 2814 | /// 2815 | WIM_MSG_PROCESS, 2816 | /// 2817 | ///Indicates that volume information is being gathered during an image capture. 2818 | /// 2819 | WIM_MSG_SCANNING, 2820 | /// 2821 | ///Indicates the number of files that will be captured or applied. 2822 | /// 2823 | WIM_MSG_SETRANGE, 2824 | /// 2825 | ///Indicates the number of files that have been captured or applied. 2826 | /// 2827 | WIM_MSG_SETPOS, 2828 | /// 2829 | ///Indicates that a file has been either captured or applied. 2830 | /// 2831 | WIM_MSG_STEPIT, 2832 | /// 2833 | ///Enables the caller to prevent a file resource from being compressed during a capture. 2834 | /// 2835 | WIM_MSG_COMPRESS, 2836 | /// 2837 | ///Alerts the caller that an error has occurred while capturing or applying an image. 2838 | /// 2839 | WIM_MSG_ERROR, 2840 | /// 2841 | ///Enables the caller to align a file resource on a particular alignment boundary. 2842 | /// 2843 | WIM_MSG_ALIGNMENT, 2844 | WIM_MSG_RETRY, 2845 | /// 2846 | ///Enables the caller to align a file resource on a particular alignment boundary. 2847 | /// 2848 | WIM_MSG_SPLIT, 2849 | WIM_MSG_SUCCESS = 0x00000000, 2850 | WIM_MSG_ABORT_IMAGE = 0xFFFFFFFF 2851 | } 2852 | 2853 | internal enum 2854 | WimCreationDisposition : uint 2855 | { 2856 | WimOpenExisting = 0x00000003, 2857 | } 2858 | 2859 | internal enum 2860 | WimActionFlags : uint 2861 | { 2862 | WimIgnored = 0x00000000 2863 | } 2864 | 2865 | internal enum 2866 | WimCompressionType : uint 2867 | { 2868 | WimIgnored = 0x00000000 2869 | } 2870 | 2871 | internal enum 2872 | WimCreationResult : uint 2873 | { 2874 | WimCreatedNew = 0x00000000, 2875 | WimOpenedExisting = 0x00000001 2876 | } 2877 | 2878 | #endregion WIMGAPI 2879 | 2880 | #endregion Enums and Flags 2881 | 2882 | #region Structs 2883 | 2884 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 2885 | public struct CreateVirtualDiskParameters 2886 | { 2887 | /// 2888 | /// A CREATE_VIRTUAL_DISK_VERSION enumeration that specifies the version of the CREATE_VIRTUAL_DISK_PARAMETERS structure being passed to or from the virtual hard disk (VHD) functions. 2889 | /// 2890 | public CreateVirtualDiskVersion Version; 2891 | 2892 | /// 2893 | /// Unique identifier to assign to the virtual disk object. If this member is set to zero, a unique identifier is created by the system. 2894 | /// 2895 | public Guid UniqueId; 2896 | 2897 | /// 2898 | /// The maximum virtual size of the virtual disk object. Must be a multiple of 512. 2899 | /// If a ParentPath is specified, this value must be zero. 2900 | /// If a SourcePath is specified, this value can be zero to specify the size of the source VHD to be used, otherwise the size specified must be greater than or equal to the size of the source disk. 2901 | /// 2902 | public ulong MaximumSize; 2903 | 2904 | /// 2905 | /// Internal size of the virtual disk object blocks. 2906 | /// The following are predefined block sizes and their behaviors. For a fixed VHD type, this parameter must be zero. 2907 | /// 2908 | public uint BlockSizeInBytes; 2909 | 2910 | /// 2911 | /// Internal size of the virtual disk object sectors. Must be set to 512. 2912 | /// 2913 | public uint SectorSizeInBytes; 2914 | 2915 | /// 2916 | /// Optional path to a parent virtual disk object. Associates the new virtual disk with an existing virtual disk. 2917 | /// If this parameter is not NULL, SourcePath must be NULL. 2918 | /// 2919 | public string ParentPath; 2920 | 2921 | /// 2922 | /// Optional path to pre-populate the new virtual disk object with block data from an existing disk. This path may refer to a VHD or a physical disk. 2923 | /// If this parameter is not NULL, ParentPath must be NULL. 2924 | /// 2925 | public string SourcePath; 2926 | 2927 | /// 2928 | /// Flags for opening the VHD 2929 | /// 2930 | public OpenVirtualDiskFlags OpenFlags; 2931 | 2932 | /// 2933 | /// GetInfoOnly flag for V2 handles 2934 | /// 2935 | public bool GetInfoOnly; 2936 | 2937 | /// 2938 | /// Virtual Storage Type of the parent disk 2939 | /// 2940 | public VirtualStorageType ParentVirtualStorageType; 2941 | 2942 | /// 2943 | /// Virtual Storage Type of the source disk 2944 | /// 2945 | public VirtualStorageType SourceVirtualStorageType; 2946 | 2947 | /// 2948 | /// A GUID to use for fallback resiliency over SMB. 2949 | /// 2950 | public Guid ResiliencyGuid; 2951 | } 2952 | 2953 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 2954 | public struct VirtualStorageType 2955 | { 2956 | public VirtualStorageDeviceType DeviceId; 2957 | public Guid VendorId; 2958 | } 2959 | 2960 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 2961 | public struct SecurityDescriptor 2962 | { 2963 | public byte revision; 2964 | public byte size; 2965 | public short control; 2966 | public IntPtr owner; 2967 | public IntPtr group; 2968 | public IntPtr sacl; 2969 | public IntPtr dacl; 2970 | } 2971 | 2972 | #endregion Structs 2973 | 2974 | #region VirtDisk.DLL P/Invoke 2975 | 2976 | [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] 2977 | public static extern uint 2978 | CreateVirtualDisk( 2979 | [In, Out] ref VirtualStorageType VirtualStorageType, 2980 | [In] string Path, 2981 | [In] VirtualDiskAccessMask VirtualDiskAccessMask, 2982 | [In, Out] ref SecurityDescriptor SecurityDescriptor, 2983 | [In] CreateVirtualDiskFlags Flags, 2984 | [In] uint ProviderSpecificFlags, 2985 | [In, Out] ref CreateVirtualDiskParameters Parameters, 2986 | [In] IntPtr Overlapped, 2987 | [Out] out SafeFileHandle Handle); 2988 | 2989 | #endregion VirtDisk.DLL P/Invoke 2990 | 2991 | #region Win32 P/Invoke 2992 | 2993 | [DllImport("advapi32", SetLastError = true)] 2994 | public static extern bool InitializeSecurityDescriptor( 2995 | [Out] out SecurityDescriptor pSecurityDescriptor, 2996 | [In] uint dwRevision); 2997 | 2998 | #endregion Win32 P/Invoke 2999 | 3000 | #region WIMGAPI P/Invoke 3001 | 3002 | #region SafeHandle wrappers for WimFileHandle and WimImageHandle 3003 | 3004 | public sealed class WimFileHandle : SafeHandle 3005 | { 3006 | 3007 | public WimFileHandle( 3008 | string wimPath) 3009 | : base(IntPtr.Zero, true) 3010 | { 3011 | 3012 | if (String.IsNullOrEmpty(wimPath)) 3013 | { 3014 | throw new ArgumentNullException("wimPath"); 3015 | } 3016 | 3017 | if (!File.Exists(Path.GetFullPath(wimPath))) 3018 | { 3019 | throw new FileNotFoundException((new FileNotFoundException()).Message, wimPath); 3020 | } 3021 | 3022 | NativeMethods.WimCreationResult creationResult; 3023 | 3024 | this.handle = NativeMethods.WimCreateFile( 3025 | wimPath, 3026 | NativeMethods.WimCreateFileDesiredAccess.WimGenericRead, 3027 | NativeMethods.WimCreationDisposition.WimOpenExisting, 3028 | NativeMethods.WimActionFlags.WimIgnored, 3029 | NativeMethods.WimCompressionType.WimIgnored, 3030 | out creationResult 3031 | ); 3032 | 3033 | // Check results. 3034 | if (creationResult != NativeMethods.WimCreationResult.WimOpenedExisting) 3035 | { 3036 | throw new Win32Exception(); 3037 | } 3038 | 3039 | if (this.handle == IntPtr.Zero) 3040 | { 3041 | throw new Win32Exception(); 3042 | } 3043 | 3044 | // Set the temporary path. 3045 | NativeMethods.WimSetTemporaryPath( 3046 | this, 3047 | Environment.ExpandEnvironmentVariables("%TEMP%") 3048 | ); 3049 | } 3050 | 3051 | protected override bool ReleaseHandle() 3052 | { 3053 | return NativeMethods.WimCloseHandle(this.handle); 3054 | } 3055 | 3056 | public override bool IsInvalid 3057 | { 3058 | get { return this.handle == IntPtr.Zero; } 3059 | } 3060 | } 3061 | 3062 | public sealed class WimImageHandle : SafeHandle 3063 | { 3064 | public WimImageHandle( 3065 | WimFile Container, 3066 | uint ImageIndex) 3067 | : base(IntPtr.Zero, true) 3068 | { 3069 | 3070 | if (null == Container) 3071 | { 3072 | throw new ArgumentNullException("Container"); 3073 | } 3074 | 3075 | if ((Container.Handle.IsClosed) || (Container.Handle.IsInvalid)) 3076 | { 3077 | throw new ArgumentNullException("The handle to the WIM file has already been closed, or is invalid.", "Container"); 3078 | } 3079 | 3080 | if (ImageIndex > Container.ImageCount) 3081 | { 3082 | throw new ArgumentOutOfRangeException("ImageIndex", "The index does not exist in the specified WIM file."); 3083 | } 3084 | 3085 | this.handle = NativeMethods.WimLoadImage( 3086 | Container.Handle.DangerousGetHandle(), 3087 | ImageIndex); 3088 | } 3089 | 3090 | protected override bool ReleaseHandle() 3091 | { 3092 | return NativeMethods.WimCloseHandle(this.handle); 3093 | } 3094 | 3095 | public override bool IsInvalid 3096 | { 3097 | get { return this.handle == IntPtr.Zero; } 3098 | } 3099 | } 3100 | 3101 | #endregion SafeHandle wrappers for WimFileHandle and WimImageHandle 3102 | 3103 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMCreateFile")] 3104 | internal static extern IntPtr 3105 | WimCreateFile( 3106 | [In, MarshalAs(UnmanagedType.LPWStr)] string WimPath, 3107 | [In] WimCreateFileDesiredAccess DesiredAccess, 3108 | [In] WimCreationDisposition CreationDisposition, 3109 | [In] WimActionFlags FlagsAndAttributes, 3110 | [In] WimCompressionType CompressionType, 3111 | [Out, Optional] out WimCreationResult CreationResult 3112 | ); 3113 | 3114 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMCloseHandle")] 3115 | [return: MarshalAs(UnmanagedType.Bool)] 3116 | internal static extern bool 3117 | WimCloseHandle( 3118 | [In] IntPtr Handle 3119 | ); 3120 | 3121 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMLoadImage")] 3122 | internal static extern IntPtr 3123 | WimLoadImage( 3124 | [In] IntPtr Handle, 3125 | [In] uint ImageIndex 3126 | ); 3127 | 3128 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMGetImageCount")] 3129 | internal static extern uint 3130 | WimGetImageCount( 3131 | [In] WimFileHandle Handle 3132 | ); 3133 | 3134 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMGetImageInformation")] 3135 | [return: MarshalAs(UnmanagedType.Bool)] 3136 | internal static extern bool 3137 | WimGetImageInformation( 3138 | [In] SafeHandle Handle, 3139 | [Out] out StringBuilder ImageInfo, 3140 | [Out] out uint SizeOfImageInfo 3141 | ); 3142 | 3143 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMSetTemporaryPath")] 3144 | [return: MarshalAs(UnmanagedType.Bool)] 3145 | internal static extern bool 3146 | WimSetTemporaryPath( 3147 | [In] WimFileHandle Handle, 3148 | [In] string TempPath 3149 | ); 3150 | 3151 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMRegisterMessageCallback", CallingConvention = CallingConvention.StdCall)] 3152 | internal static extern uint 3153 | WimRegisterMessageCallback( 3154 | [In, Optional] WimFileHandle hWim, 3155 | [In] WimMessageCallback MessageProc, 3156 | [In, Optional] IntPtr ImageInfo 3157 | ); 3158 | 3159 | [DllImport("Wimgapi.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "WIMUnregisterMessageCallback", CallingConvention = CallingConvention.StdCall)] 3160 | [return: MarshalAs(UnmanagedType.Bool)] 3161 | internal static extern bool 3162 | WimUnregisterMessageCallback( 3163 | [In, Optional] WimFileHandle hWim, 3164 | [In] WimMessageCallback MessageProc 3165 | ); 3166 | 3167 | 3168 | #endregion WIMGAPI P/Invoke 3169 | } 3170 | 3171 | #region WIM Interop 3172 | 3173 | public class WimFile 3174 | { 3175 | 3176 | internal XDocument m_xmlInfo; 3177 | internal List m_imageList; 3178 | 3179 | private static NativeMethods.WimMessageCallback wimMessageCallback; 3180 | 3181 | #region Events 3182 | 3183 | /// 3184 | /// DefaultImageEvent handler 3185 | /// 3186 | public delegate void DefaultImageEventHandler(object sender, DefaultImageEventArgs e); 3187 | 3188 | /// 3189 | ///ProcessFileEvent handler 3190 | /// 3191 | public delegate void ProcessFileEventHandler(object sender, ProcessFileEventArgs e); 3192 | 3193 | /// 3194 | ///Enable the caller to prevent a file resource from being compressed during a capture. 3195 | /// 3196 | public event ProcessFileEventHandler ProcessFileEvent; 3197 | 3198 | /// 3199 | ///Indicate an update in the progress of an image application. 3200 | /// 3201 | public event DefaultImageEventHandler ProgressEvent; 3202 | 3203 | /// 3204 | ///Alert the caller that an error has occurred while capturing or applying an image. 3205 | /// 3206 | public event DefaultImageEventHandler ErrorEvent; 3207 | 3208 | /// 3209 | ///Indicate that a file has been either captured or applied. 3210 | /// 3211 | public event DefaultImageEventHandler StepItEvent; 3212 | 3213 | /// 3214 | ///Indicate the number of files that will be captured or applied. 3215 | /// 3216 | public event DefaultImageEventHandler SetRangeEvent; 3217 | 3218 | /// 3219 | ///Indicate the number of files that have been captured or applied. 3220 | /// 3221 | public event DefaultImageEventHandler SetPosEvent; 3222 | 3223 | #endregion Events 3224 | 3225 | private 3226 | enum 3227 | ImageEventMessage : uint 3228 | { 3229 | /// 3230 | ///Enables the caller to prevent a file or a directory from being captured or applied. 3231 | /// 3232 | Progress = NativeMethods.WimMessage.WIM_MSG_PROGRESS, 3233 | /// 3234 | ///Notification sent to enable the caller to prevent a file or a directory from being captured or applied. 3235 | ///To prevent a file or a directory from being captured or applied, call WindowsImageContainer.SkipFile(). 3236 | /// 3237 | Process = NativeMethods.WimMessage.WIM_MSG_PROCESS, 3238 | /// 3239 | ///Enables the caller to prevent a file resource from being compressed during a capture. 3240 | /// 3241 | Compress = NativeMethods.WimMessage.WIM_MSG_COMPRESS, 3242 | /// 3243 | ///Alerts the caller that an error has occurred while capturing or applying an image. 3244 | /// 3245 | Error = NativeMethods.WimMessage.WIM_MSG_ERROR, 3246 | /// 3247 | ///Enables the caller to align a file resource on a particular alignment boundary. 3248 | /// 3249 | Alignment = NativeMethods.WimMessage.WIM_MSG_ALIGNMENT, 3250 | /// 3251 | ///Enables the caller to align a file resource on a particular alignment boundary. 3252 | /// 3253 | Split = NativeMethods.WimMessage.WIM_MSG_SPLIT, 3254 | /// 3255 | ///Indicates that volume information is being gathered during an image capture. 3256 | /// 3257 | Scanning = NativeMethods.WimMessage.WIM_MSG_SCANNING, 3258 | /// 3259 | ///Indicates the number of files that will be captured or applied. 3260 | /// 3261 | SetRange = NativeMethods.WimMessage.WIM_MSG_SETRANGE, 3262 | /// 3263 | ///Indicates the number of files that have been captured or applied. 3264 | /// 3265 | SetPos = NativeMethods.WimMessage.WIM_MSG_SETPOS, 3266 | /// 3267 | ///Indicates that a file has been either captured or applied. 3268 | /// 3269 | StepIt = NativeMethods.WimMessage.WIM_MSG_STEPIT, 3270 | /// 3271 | ///Success. 3272 | /// 3273 | Success = NativeMethods.WimMessage.WIM_MSG_SUCCESS, 3274 | /// 3275 | ///Abort. 3276 | /// 3277 | Abort = NativeMethods.WimMessage.WIM_MSG_ABORT_IMAGE 3278 | } 3279 | 3280 | /// 3281 | ///Event callback to the Wimgapi events 3282 | /// 3283 | private 3284 | uint 3285 | ImageEventMessagePump( 3286 | uint MessageId, 3287 | IntPtr wParam, 3288 | IntPtr lParam, 3289 | IntPtr UserData) 3290 | { 3291 | 3292 | uint status = (uint) NativeMethods.WimMessage.WIM_MSG_SUCCESS; 3293 | 3294 | DefaultImageEventArgs eventArgs = new DefaultImageEventArgs(wParam, lParam, UserData); 3295 | 3296 | switch ((ImageEventMessage)MessageId) 3297 | { 3298 | 3299 | case ImageEventMessage.Progress: 3300 | ProgressEvent(this, eventArgs); 3301 | break; 3302 | 3303 | case ImageEventMessage.Process: 3304 | if (null != ProcessFileEvent) 3305 | { 3306 | string fileToImage = Marshal.PtrToStringUni(wParam); 3307 | ProcessFileEventArgs fileToProcess = new ProcessFileEventArgs(fileToImage, lParam); 3308 | ProcessFileEvent(this, fileToProcess); 3309 | 3310 | if (fileToProcess.Abort == true) 3311 | { 3312 | status = (uint)ImageEventMessage.Abort; 3313 | } 3314 | } 3315 | break; 3316 | 3317 | case ImageEventMessage.Error: 3318 | if (null != ErrorEvent) 3319 | { 3320 | ErrorEvent(this, eventArgs); 3321 | } 3322 | break; 3323 | 3324 | case ImageEventMessage.SetRange: 3325 | if (null != SetRangeEvent) 3326 | { 3327 | SetRangeEvent(this, eventArgs); 3328 | } 3329 | break; 3330 | 3331 | case ImageEventMessage.SetPos: 3332 | if (null != SetPosEvent) 3333 | { 3334 | SetPosEvent(this, eventArgs); 3335 | } 3336 | break; 3337 | 3338 | case ImageEventMessage.StepIt: 3339 | if (null != StepItEvent) 3340 | { 3341 | StepItEvent(this, eventArgs); 3342 | } 3343 | break; 3344 | 3345 | default: 3346 | break; 3347 | } 3348 | return status; 3349 | 3350 | } 3351 | 3352 | /// 3353 | /// Constructor. 3354 | /// 3355 | /// Path to the WIM container. 3356 | public 3357 | WimFile(string wimPath) 3358 | { 3359 | if (string.IsNullOrEmpty(wimPath)) 3360 | { 3361 | throw new ArgumentNullException("wimPath"); 3362 | } 3363 | 3364 | if (!File.Exists(Path.GetFullPath(wimPath))) 3365 | { 3366 | throw new FileNotFoundException((new FileNotFoundException()).Message, wimPath); 3367 | } 3368 | 3369 | Handle = new NativeMethods.WimFileHandle(wimPath); 3370 | 3371 | // Hook up the events before we return. 3372 | //wimMessageCallback = new NativeMethods.WimMessageCallback(ImageEventMessagePump); 3373 | //NativeMethods.RegisterMessageCallback(this.Handle, wimMessageCallback); 3374 | } 3375 | 3376 | /// 3377 | /// Closes the WIM file. 3378 | /// 3379 | public void 3380 | Close() 3381 | { 3382 | foreach (WimImage image in Images) 3383 | { 3384 | image.Close(); 3385 | } 3386 | 3387 | if (null != wimMessageCallback) 3388 | { 3389 | NativeMethods.UnregisterMessageCallback(this.Handle, wimMessageCallback); 3390 | wimMessageCallback = null; 3391 | } 3392 | 3393 | if ((!Handle.IsClosed) && (!Handle.IsInvalid)) 3394 | { 3395 | Handle.Close(); 3396 | } 3397 | } 3398 | 3399 | /// 3400 | /// Provides a list of WimImage objects, representing the images in the WIM container file. 3401 | /// 3402 | public List 3403 | Images 3404 | { 3405 | get 3406 | { 3407 | if (null == m_imageList) 3408 | { 3409 | 3410 | int imageCount = (int)ImageCount; 3411 | m_imageList = new List(imageCount); 3412 | for (int i = 0; i < imageCount; i++) 3413 | { 3414 | 3415 | // Load up each image so it's ready for us. 3416 | m_imageList.Add( 3417 | new WimImage(this, (uint)i + 1)); 3418 | } 3419 | } 3420 | 3421 | return m_imageList; 3422 | } 3423 | } 3424 | 3425 | /// 3426 | /// Provides a list of names of the images in the specified WIM container file. 3427 | /// 3428 | public List 3429 | ImageNames 3430 | { 3431 | get 3432 | { 3433 | List nameList = new List(); 3434 | foreach (WimImage image in Images) 3435 | { 3436 | nameList.Add(image.ImageName); 3437 | } 3438 | return nameList; 3439 | } 3440 | } 3441 | 3442 | /// 3443 | /// Indexer for WIM images inside the WIM container, indexed by the image number. 3444 | /// The list of Images is 0-based, but the WIM container is 1-based, so we automatically compensate for that. 3445 | /// this[1] returns the 0th image in the WIM container. 3446 | /// 3447 | /// The 1-based index of the image to retrieve. 3448 | /// WinImage object. 3449 | public WimImage 3450 | this[int ImageIndex] 3451 | { 3452 | get { return Images[ImageIndex - 1]; } 3453 | } 3454 | 3455 | /// 3456 | /// Indexer for WIM images inside the WIM container, indexed by the image name. 3457 | /// WIMs created by different processes sometimes contain different information - including the name. 3458 | /// Some images have their name stored in the Name field, some in the Flags field, and some in the EditionID field. 3459 | /// We take all of those into account in while searching the WIM. 3460 | /// 3461 | /// 3462 | /// 3463 | public WimImage 3464 | this[string ImageName] 3465 | { 3466 | get 3467 | { 3468 | return 3469 | Images.Where(i => ( 3470 | i.ImageName.ToUpper() == ImageName.ToUpper() || 3471 | i.ImageFlags.ToUpper() == ImageName.ToUpper() )) 3472 | .DefaultIfEmpty(null) 3473 | .FirstOrDefault(); 3474 | } 3475 | } 3476 | 3477 | /// 3478 | /// Returns the number of images in the WIM container. 3479 | /// 3480 | internal uint 3481 | ImageCount 3482 | { 3483 | get { return NativeMethods.WimGetImageCount(Handle); } 3484 | } 3485 | 3486 | /// 3487 | /// Returns an XDocument representation of the XML metadata for the WIM container and associated images. 3488 | /// 3489 | internal XDocument 3490 | XmlInfo 3491 | { 3492 | get 3493 | { 3494 | 3495 | if (null == m_xmlInfo) 3496 | { 3497 | StringBuilder builder; 3498 | uint bytes; 3499 | if (!NativeMethods.WimGetImageInformation(Handle, out builder, out bytes)) 3500 | { 3501 | throw new Win32Exception(); 3502 | } 3503 | 3504 | // Ensure the length of the returned bytes to avoid garbage characters at the end. 3505 | int charCount = (int)bytes / sizeof(char); 3506 | if (null != builder) 3507 | { 3508 | // Get rid of the unicode file marker at the beginning of the XML. 3509 | builder.Remove(0, 1); 3510 | builder.EnsureCapacity(charCount - 1); 3511 | builder.Length = charCount - 1; 3512 | 3513 | // This isn't likely to change while we have the image open, so cache it. 3514 | m_xmlInfo = XDocument.Parse(builder.ToString().Trim()); 3515 | } 3516 | else 3517 | { 3518 | m_xmlInfo = null; 3519 | } 3520 | } 3521 | 3522 | return m_xmlInfo; 3523 | } 3524 | } 3525 | 3526 | public NativeMethods.WimFileHandle Handle 3527 | { 3528 | get; 3529 | private set; 3530 | } 3531 | } 3532 | 3533 | public class 3534 | WimImage 3535 | { 3536 | 3537 | internal XDocument m_xmlInfo; 3538 | 3539 | public 3540 | WimImage( 3541 | WimFile Container, 3542 | uint ImageIndex) 3543 | { 3544 | 3545 | if (null == Container) 3546 | { 3547 | throw new ArgumentNullException("Container"); 3548 | } 3549 | 3550 | if ((Container.Handle.IsClosed) || (Container.Handle.IsInvalid)) 3551 | { 3552 | throw new ArgumentNullException("The handle to the WIM file has already been closed, or is invalid.", "Container"); 3553 | } 3554 | 3555 | if (ImageIndex > Container.ImageCount) 3556 | { 3557 | throw new ArgumentOutOfRangeException("ImageIndex", "The index does not exist in the specified WIM file."); 3558 | } 3559 | 3560 | Handle = new NativeMethods.WimImageHandle(Container, ImageIndex); 3561 | } 3562 | 3563 | public enum 3564 | Architectures : uint 3565 | { 3566 | x86 = 0x0, 3567 | ARM = 0x5, 3568 | IA64 = 0x6, 3569 | AMD64 = 0x9, 3570 | ARM64 = 0xC 3571 | } 3572 | 3573 | public void 3574 | Close() 3575 | { 3576 | if ((!Handle.IsClosed) && (!Handle.IsInvalid)) 3577 | { 3578 | Handle.Close(); 3579 | } 3580 | } 3581 | 3582 | public NativeMethods.WimImageHandle 3583 | Handle 3584 | { 3585 | get; 3586 | private set; 3587 | } 3588 | 3589 | internal XDocument 3590 | XmlInfo 3591 | { 3592 | get 3593 | { 3594 | 3595 | if (null == m_xmlInfo) 3596 | { 3597 | StringBuilder builder; 3598 | uint bytes; 3599 | if (!NativeMethods.WimGetImageInformation(Handle, out builder, out bytes)) 3600 | { 3601 | throw new Win32Exception(); 3602 | } 3603 | 3604 | // Ensure the length of the returned bytes to avoid garbage characters at the end. 3605 | int charCount = (int)bytes / sizeof(char); 3606 | if (null != builder) 3607 | { 3608 | // Get rid of the unicode file marker at the beginning of the XML. 3609 | builder.Remove(0, 1); 3610 | builder.EnsureCapacity(charCount - 1); 3611 | builder.Length = charCount - 1; 3612 | 3613 | // This isn't likely to change while we have the image open, so cache it. 3614 | m_xmlInfo = XDocument.Parse(builder.ToString().Trim()); 3615 | } 3616 | else 3617 | { 3618 | m_xmlInfo = null; 3619 | } 3620 | } 3621 | 3622 | return m_xmlInfo; 3623 | } 3624 | } 3625 | 3626 | public string 3627 | ImageIndex 3628 | { 3629 | get { return XmlInfo.Element("IMAGE").Attribute("INDEX").Value; } 3630 | } 3631 | 3632 | public string 3633 | ImageName 3634 | { 3635 | get { return XmlInfo.XPathSelectElement("/IMAGE/NAME").Value; } 3636 | } 3637 | 3638 | public string 3639 | ImageEditionId 3640 | { 3641 | get { return XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/EDITIONID").Value; } 3642 | } 3643 | 3644 | public string 3645 | ImageFlags 3646 | { 3647 | get 3648 | { 3649 | string flagValue = String.Empty; 3650 | 3651 | try 3652 | { 3653 | flagValue = XmlInfo.XPathSelectElement("/IMAGE/FLAGS").Value; 3654 | } 3655 | catch 3656 | { 3657 | 3658 | // Some WIM files don't contain a FLAGS element in the metadata. 3659 | // In an effort to support those WIMs too, inherit the EditionId if there 3660 | // are no Flags. 3661 | 3662 | if (String.IsNullOrEmpty(flagValue)) 3663 | { 3664 | flagValue = this.ImageEditionId; 3665 | 3666 | // Check to see if the EditionId is "ServerHyper". If so, 3667 | // tweak it to be "ServerHyperCore" instead. 3668 | 3669 | if (0 == String.Compare("serverhyper", flagValue, true)) 3670 | { 3671 | flagValue = "ServerHyperCore"; 3672 | } 3673 | } 3674 | 3675 | } 3676 | 3677 | return flagValue; 3678 | } 3679 | } 3680 | 3681 | public string 3682 | ImageProductType 3683 | { 3684 | get { return XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/PRODUCTTYPE").Value; } 3685 | } 3686 | 3687 | public string 3688 | ImageInstallationType 3689 | { 3690 | get { return XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/INSTALLATIONTYPE").Value; } 3691 | } 3692 | 3693 | public string 3694 | ImageDescription 3695 | { 3696 | get { return XmlInfo.XPathSelectElement("/IMAGE/DESCRIPTION").Value; } 3697 | } 3698 | 3699 | public ulong 3700 | ImageSize 3701 | { 3702 | get { return ulong.Parse(XmlInfo.XPathSelectElement("/IMAGE/TOTALBYTES").Value); } 3703 | } 3704 | 3705 | public Architectures 3706 | ImageArchitecture 3707 | { 3708 | get 3709 | { 3710 | int arch = -1; 3711 | try 3712 | { 3713 | arch = int.Parse(XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/ARCH").Value); 3714 | } 3715 | catch { } 3716 | 3717 | return (Architectures)arch; 3718 | } 3719 | } 3720 | 3721 | public string 3722 | ImageDefaultLanguage 3723 | { 3724 | get 3725 | { 3726 | string lang = null; 3727 | try 3728 | { 3729 | lang = XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/LANGUAGES/DEFAULT").Value; 3730 | } 3731 | catch { } 3732 | 3733 | return lang; 3734 | } 3735 | } 3736 | 3737 | public Version 3738 | ImageVersion 3739 | { 3740 | get 3741 | { 3742 | int major = 0; 3743 | int minor = 0; 3744 | int build = 0; 3745 | int revision = 0; 3746 | 3747 | try 3748 | { 3749 | major = int.Parse(XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/VERSION/MAJOR").Value); 3750 | minor = int.Parse(XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/VERSION/MINOR").Value); 3751 | build = int.Parse(XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/VERSION/BUILD").Value); 3752 | revision = int.Parse(XmlInfo.XPathSelectElement("/IMAGE/WINDOWS/VERSION/SPBUILD").Value); 3753 | } 3754 | catch { } 3755 | 3756 | return (new Version(major, minor, build, revision)); 3757 | } 3758 | } 3759 | 3760 | public string 3761 | ImageDisplayName 3762 | { 3763 | get { return XmlInfo.XPathSelectElement("/IMAGE/DISPLAYNAME").Value; } 3764 | } 3765 | 3766 | public string 3767 | ImageDisplayDescription 3768 | { 3769 | get { return XmlInfo.XPathSelectElement("/IMAGE/DISPLAYDESCRIPTION").Value; } 3770 | } 3771 | } 3772 | 3773 | /// 3774 | ///Describes the file that is being processed for the ProcessFileEvent. 3775 | /// 3776 | public class 3777 | DefaultImageEventArgs : EventArgs 3778 | { 3779 | /// 3780 | ///Default constructor. 3781 | /// 3782 | public 3783 | DefaultImageEventArgs( 3784 | IntPtr wideParameter, 3785 | IntPtr leftParameter, 3786 | IntPtr userData) 3787 | { 3788 | 3789 | WideParameter = wideParameter; 3790 | LeftParameter = leftParameter; 3791 | UserData = userData; 3792 | } 3793 | 3794 | /// 3795 | ///wParam 3796 | /// 3797 | public IntPtr WideParameter 3798 | { 3799 | get; 3800 | private set; 3801 | } 3802 | 3803 | /// 3804 | ///lParam 3805 | /// 3806 | public IntPtr LeftParameter 3807 | { 3808 | get; 3809 | private set; 3810 | } 3811 | 3812 | /// 3813 | ///UserData 3814 | /// 3815 | public IntPtr UserData 3816 | { 3817 | get; 3818 | private set; 3819 | } 3820 | } 3821 | 3822 | /// 3823 | ///Describes the file that is being processed for the ProcessFileEvent. 3824 | /// 3825 | public class 3826 | ProcessFileEventArgs : EventArgs 3827 | { 3828 | /// 3829 | ///Default constructor. 3830 | /// 3831 | ///Fully qualified path and file name. For example: c:\file.sys. 3832 | ///Default is false - skip file and continue. 3833 | ///Set to true to abort the entire image capture. 3834 | public 3835 | ProcessFileEventArgs( 3836 | string file, 3837 | IntPtr skipFileFlag) 3838 | { 3839 | 3840 | m_FilePath = file; 3841 | m_SkipFileFlag = skipFileFlag; 3842 | } 3843 | 3844 | /// 3845 | ///Skip file from being imaged. 3846 | /// 3847 | public void 3848 | SkipFile() 3849 | { 3850 | byte[] byteBuffer = 3851 | { 3852 | 0 3853 | }; 3854 | int byteBufferSize = byteBuffer.Length; 3855 | Marshal.Copy(byteBuffer, 0, m_SkipFileFlag, byteBufferSize); 3856 | } 3857 | 3858 | /// 3859 | ///Fully qualified path and file name. 3860 | /// 3861 | public string 3862 | FilePath 3863 | { 3864 | get 3865 | { 3866 | string stringToReturn = ""; 3867 | if (m_FilePath != null) 3868 | { 3869 | stringToReturn = m_FilePath; 3870 | } 3871 | return stringToReturn; 3872 | } 3873 | } 3874 | 3875 | /// 3876 | ///Flag to indicate if the entire image capture should be aborted. 3877 | ///Default is false - skip file and continue. Setting to true will 3878 | ///abort the entire image capture. 3879 | /// 3880 | public bool Abort 3881 | { 3882 | set { m_Abort = value; } 3883 | get { return m_Abort; } 3884 | } 3885 | 3886 | private string m_FilePath; 3887 | private bool m_Abort; 3888 | private IntPtr m_SkipFileFlag; 3889 | 3890 | } 3891 | 3892 | #endregion WIM Interop 3893 | 3894 | #region VHD Interop 3895 | // Based on code written by the Hyper-V Test team. 3896 | /// 3897 | /// The Virtual Hard Disk class provides methods for creating and manipulating Virtual Hard Disk files. 3898 | /// 3899 | public class 3900 | VirtualHardDisk 3901 | { 3902 | #region Static Methods 3903 | 3904 | #region Sparse Disks 3905 | 3906 | /// 3907 | /// Abbreviated signature of CreateSparseDisk so it's easier to use from WIM2VHD. 3908 | /// 3909 | /// The type of disk to create, VHD or VHDX. 3910 | /// The path of the disk to create. 3911 | /// The maximum size of the disk to create. 3912 | /// Overwrite the VHD if it already exists. 3913 | public static void 3914 | CreateSparseDisk( 3915 | NativeMethods.VirtualStorageDeviceType virtualStorageDeviceType, 3916 | string path, 3917 | ulong size, 3918 | bool overwrite) 3919 | { 3920 | 3921 | CreateSparseDisk( 3922 | path, 3923 | size, 3924 | overwrite, 3925 | null, 3926 | IntPtr.Zero, 3927 | (virtualStorageDeviceType == NativeMethods.VirtualStorageDeviceType.VHD) 3928 | ? NativeMethods.DEFAULT_BLOCK_SIZE 3929 | : 0, 3930 | virtualStorageDeviceType, 3931 | NativeMethods.DISK_SECTOR_SIZE); 3932 | } 3933 | 3934 | /// 3935 | /// Creates a new sparse (dynamically expanding) virtual hard disk (.vhd). Supports both sync and async modes. 3936 | /// The VHD image file uses only as much space on the backing store as needed to store the actual data the VHD currently contains. 3937 | /// 3938 | /// The path and name of the VHD to create. 3939 | /// The size of the VHD to create in bytes. 3940 | /// When creating this type of VHD, the VHD API does not test for free space on the physical backing store based on the maximum size requested, 3941 | /// therefore it is possible to successfully create a dynamic VHD with a maximum size larger than the available physical disk free space. 3942 | /// The maximum size of a dynamic VHD is 2,040 GB. The minimum size is 3 MB. 3943 | /// Optional path to pre-populate the new virtual disk object with block data from an existing disk 3944 | /// This path may refer to a VHD or a physical disk. Use NULL if you don't want a source. 3945 | /// If the VHD exists, setting this parameter to 'True' will delete it and create a new one. 3946 | /// If not null, the operation runs in async mode 3947 | /// Block size for the VHD. 3948 | /// VHD format version (VHD1 or VHD2) 3949 | /// Sector size for the VHD. 3950 | /// Thrown when an invalid size is specified 3951 | /// Thrown when source VHD is not found. 3952 | /// Thrown when there was an error while creating the default security descriptor. 3953 | /// Thrown when an error occurred while creating the VHD. 3954 | public static void 3955 | CreateSparseDisk( 3956 | string path, 3957 | ulong size, 3958 | bool overwrite, 3959 | string source, 3960 | IntPtr overlapped, 3961 | uint blockSizeInBytes, 3962 | NativeMethods.VirtualStorageDeviceType virtualStorageDeviceType, 3963 | uint sectorSizeInBytes) 3964 | { 3965 | 3966 | // Validate the virtualStorageDeviceType 3967 | if (virtualStorageDeviceType != NativeMethods.VirtualStorageDeviceType.VHD && virtualStorageDeviceType != NativeMethods.VirtualStorageDeviceType.VHDX) 3968 | { 3969 | 3970 | throw ( 3971 | new ArgumentOutOfRangeException( 3972 | "virtualStorageDeviceType", 3973 | virtualStorageDeviceType, 3974 | "VirtualStorageDeviceType must be VHD or VHDX." 3975 | )); 3976 | } 3977 | 3978 | // Validate size. It needs to be a multiple of DISK_SECTOR_SIZE (512)... 3979 | if ((size % NativeMethods.DISK_SECTOR_SIZE) != 0) 3980 | { 3981 | 3982 | throw ( 3983 | new ArgumentOutOfRangeException( 3984 | "size", 3985 | size, 3986 | "The size of the virtual disk must be a multiple of 512." 3987 | )); 3988 | } 3989 | 3990 | if ((!String.IsNullOrEmpty(source)) && (!System.IO.File.Exists(source))) 3991 | { 3992 | 3993 | throw ( 3994 | new System.IO.FileNotFoundException( 3995 | "Unable to find the source file.", 3996 | source 3997 | )); 3998 | } 3999 | 4000 | if ((overwrite) && (System.IO.File.Exists(path))) 4001 | { 4002 | 4003 | System.IO.File.Delete(path); 4004 | } 4005 | 4006 | NativeMethods.CreateVirtualDiskParameters createParams = new NativeMethods.CreateVirtualDiskParameters(); 4007 | 4008 | // Select the correct version. 4009 | createParams.Version = (virtualStorageDeviceType == NativeMethods.VirtualStorageDeviceType.VHD) 4010 | ? NativeMethods.CreateVirtualDiskVersion.Version1 4011 | : NativeMethods.CreateVirtualDiskVersion.Version2; 4012 | 4013 | createParams.UniqueId = Guid.NewGuid(); 4014 | createParams.MaximumSize = size; 4015 | createParams.BlockSizeInBytes = blockSizeInBytes; 4016 | createParams.SectorSizeInBytes = sectorSizeInBytes; 4017 | createParams.ParentPath = null; 4018 | createParams.SourcePath = source; 4019 | createParams.OpenFlags = NativeMethods.OpenVirtualDiskFlags.None; 4020 | createParams.GetInfoOnly = false; 4021 | createParams.ParentVirtualStorageType = new NativeMethods.VirtualStorageType(); 4022 | createParams.SourceVirtualStorageType = new NativeMethods.VirtualStorageType(); 4023 | 4024 | // 4025 | // Create and init a security descriptor. 4026 | // Since we're creating an essentially blank SD to use with CreateVirtualDisk 4027 | // the VHD will take on the security values from the parent directory. 4028 | // 4029 | 4030 | NativeMethods.SecurityDescriptor securityDescriptor; 4031 | if (!NativeMethods.InitializeSecurityDescriptor(out securityDescriptor, 1)) 4032 | { 4033 | 4034 | throw ( 4035 | new SecurityException( 4036 | "Unable to initialize the security descriptor for the virtual disk." 4037 | )); 4038 | } 4039 | 4040 | NativeMethods.VirtualStorageType virtualStorageType = new NativeMethods.VirtualStorageType(); 4041 | virtualStorageType.DeviceId = virtualStorageDeviceType; 4042 | virtualStorageType.VendorId = NativeMethods.VirtualStorageTypeVendorMicrosoft; 4043 | 4044 | SafeFileHandle vhdHandle; 4045 | 4046 | uint returnCode = NativeMethods.CreateVirtualDisk( 4047 | ref virtualStorageType, 4048 | path, 4049 | (virtualStorageDeviceType == NativeMethods.VirtualStorageDeviceType.VHD) 4050 | ? NativeMethods.VirtualDiskAccessMask.All 4051 | : NativeMethods.VirtualDiskAccessMask.None, 4052 | ref securityDescriptor, 4053 | NativeMethods.CreateVirtualDiskFlags.None, 4054 | 0, 4055 | ref createParams, 4056 | overlapped, 4057 | out vhdHandle); 4058 | 4059 | vhdHandle.Close(); 4060 | 4061 | if (NativeMethods.ERROR_SUCCESS != returnCode && NativeMethods.ERROR_IO_PENDING != returnCode) 4062 | { 4063 | 4064 | throw ( 4065 | new Win32Exception( 4066 | (int)returnCode 4067 | )); 4068 | } 4069 | } 4070 | 4071 | #endregion Sparse Disks 4072 | 4073 | #endregion Static Methods 4074 | 4075 | } 4076 | #endregion VHD Interop 4077 | } 4078 | "@ 4079 | 4080 | Add-Type -TypeDefinition $code -ReferencedAssemblies "System.Xml","System.Linq","System.Xml.Linq" -ErrorAction SilentlyContinue 4081 | } 4082 | -------------------------------------------------------------------------------- /Hyper-ConvertImage/Hyper-ConvertImage.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabs-not-spaces/Hyper-ConvertImage/a5b16b13652869ace0fbf4caf54b02fc9ef6bfe9/Hyper-ConvertImage/Hyper-ConvertImage.psd1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyper-ConvertImage 2 | 3 | ![PowerShell Gallery](https://img.shields.io/powershellgallery/v/Hyper-ConvertImage.svg?style=flat&logo=powershell&label=PSGallery%20Version) 4 | ![PSGallery Downloads](https://img.shields.io/powershellgallery/dt/Hyper-ConvertImage.svg?style=flat&logo=powershell&label=PSGallery%20Downloads) 5 | 6 | Microsoft hasn't published any approved PRs on their [Convert-WindowsImage](https://github.com/MicrosoftDocs/Virtualization-Documentation/tree/master/hyperv-tools/Convert-WindowsImage) module in years. This is a more recent version. 7 | 8 | ## How to use 9 | 10 | ### Install the module 11 | 12 | ``` PowerShell 13 | Install-Module Hyper-ConvertImage -Scope CurrentUser 14 | ``` 15 | 16 | ### Standard Windows Image Conversion 17 | 18 | ``` PowerShell 19 | $params = @{ 20 | SourcePath = "C:\Path\To\Source.iso" 21 | Edition = 1 22 | VhdType = "Dynamic" 23 | VhdFormat = "VHDX" 24 | VhdPath = "C:\Path\To\output.vhdx" 25 | DiskLayout = "UEFI" 26 | SizeBytes = 127gb 27 | } 28 | Convert-WindowsImage @params 29 | ``` 30 | 31 | ### Windows Image Conversion w/ Unattend File 32 | 33 | ``` PowerShell 34 | $params = @{ 35 | SourcePath = "C:\Path\To\Source.iso" 36 | Edition = 1 37 | VhdType = "Dynamic" 38 | VhdFormat = "VHDX" 39 | VhdPath = "C:\Path\To\output.vhdx" 40 | DiskLayout = "UEFI" 41 | SizeBytes = 127gb 42 | UnattendPath = "C:\Path\To\Unattend.xml" 43 | } 44 | Convert-WindowsImage @params 45 | ``` --------------------------------------------------------------------------------