├── _config.yml ├── LICENSE ├── Example-BootFromNewVHD.PS1 ├── unattend_amd64_Server.xml ├── unattend_amd64_Client.xml ├── README-CN.md ├── Example-Create-OSVHDS.ps1 ├── README.md └── Deploy-VHD.PS1 /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-Present, Wei Luo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE/SOURCE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Example-BootFromNewVHD.PS1: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017- Wei Luo 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | # 23 | # SCRIPT_VERSION: 1.0.100.Main.20170218.1818.0.Release 24 | # 25 | 26 | 27 | <# 28 | .NOTES 29 | THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. 30 | THE SAMPLE SOURCE CODE IS UNDER THE MIT LICENSE. 31 | Developed by Wei Luo. 32 | 33 | .SYNOPSIS 34 | This script is used to deploy a VHD(X) file to enable boot from VHD in 35 | physical machine. The machine will restart in 30 seconds, and automatic 36 | logon on first time. 37 | 38 | It depends on the script "Deploy-VHD.ps1". It can be considered as 39 | sample script of the "Deploy-VHD.ps1". 40 | 41 | .PARAMETER NewVHDPath 42 | The name and path of the Virtual Hard Disk from which is boot. 43 | 44 | .PARAMETER TestMode 45 | Boot entry will not be added and restart will not happen. 46 | 47 | .DESCRIPTION 48 | The script deploys the VHD(X) file to the target path and enable boot 49 | from VHD function in physical machine. 50 | 51 | Auto-restart can be enabled to restart the machine in 30 seconds. 52 | 53 | 54 | .EXAMPLE 55 | .\BootFromNewVHD.ps1 -NewVHDPath C:\VHDBoot\WinServer2016.VHDX -TestMode 56 | 57 | This command will edit new VHD file WinServer2016.VHDX in folder 58 | C:\VHDBoot, and enable boot from VHD. 59 | 60 | .OUTPUTS 61 | System.IO.FileInfo 62 | #> 63 | 64 | 65 | 66 | 67 | [CmdletBinding(DefaultParametersetName="NewNativeBoot")] 68 | Param( 69 | [Parameter(Mandatory=$True)] 70 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 71 | [string]$NewVHDPath, 72 | 73 | [Parameter(Mandatory=$False)] 74 | [switch]$TestMode 75 | ) 76 | 77 | 78 | 79 | $parameters = @{ 80 | 81 | EnableNativeBoot = $True 82 | EnableAutoLogon = $True 83 | VHDPath = $NewVHDPath 84 | ChinaTime = $True 85 | TestMode = $True 86 | Restart = $True 87 | } 88 | 89 | 90 | 91 | If ($TestMode) 92 | { 93 | $parameters.Remove('Restart') 94 | 95 | } 96 | Else 97 | { 98 | $parameters.Remove('TestMode') 99 | } 100 | 101 | .\Deploy-VHD.PS1 @parameters -------------------------------------------------------------------------------- /unattend_amd64_Server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %REGISTEREDOWNER% 6 | %REGISTEREDORGANIZATION% 7 | %COMPUTERNAME% 8 | 9 | 10 | false 11 | 12 | 13 | 0 14 | 15 | 16 | 17 | 18 | Remote Desktop 19 | all 20 | true 21 | 22 | 23 | File and Printer Sharing 24 | all 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | en-US 33 | en-US 34 | en-US 35 | en-US 36 | en-US 37 | 38 | 39 | 40 | 41 | %ACCOUNTPASSWORD% 42 | false</PlainText> 43 | </Password> 44 | <Enabled>%AUTOLOGON%</Enabled> 45 | <Username>Administrator</Username> 46 | </AutoLogon> 47 | <UserAccounts> 48 | <AdministratorPassword> 49 | <Value>%ADMINPASSWORD%</Value> 50 | <PlainText>false</PlainText> 51 | </AdministratorPassword> 52 | </UserAccounts> 53 | <OOBE> 54 | <HideEULAPage>true</HideEULAPage> 55 | <SkipMachineOOBE>true</SkipMachineOOBE> 56 | </OOBE> 57 | <TimeZone>%TIMEZONE%</TimeZone> 58 | </component> 59 | </settings> 60 | <cpi:offlineImage cpi:source="catalog:d:/temp/install_windows server 2012 r2 serverdatacenter.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> 61 | </unattend> 62 | -------------------------------------------------------------------------------- /unattend_amd64_Client.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <unattend xmlns="urn:schemas-microsoft-com:unattend"> 3 | <settings pass="specialize"> 4 | <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 5 | <RegisteredOwner>%REGISTEREDOWNER%</RegisteredOwner> 6 | <RegisteredOrganization>%REGISTEREDORGANIZATION%</RegisteredOrganization> 7 | <ComputerName>%COMPUTERNAME%</ComputerName> 8 | </component> 9 | <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 10 | <fDenyTSConnections>false</fDenyTSConnections> 11 | </component> 12 | <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 13 | <UserAuthentication>0</UserAuthentication> 14 | </component> 15 | <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 16 | <FirewallGroups> 17 | <FirewallGroup wcm:action="add" wcm:keyValue="EnableRemoteDesktop"> 18 | <Group>Remote Desktop</Group> 19 | <Profile>all</Profile> 20 | <Active>true</Active> 21 | </FirewallGroup> 22 | <FirewallGroup wcm:action="add" wcm:keyValue="EnableFileAndPrinterSharing"> 23 | <Group>File and Printer Sharing</Group> 24 | <Profile>all</Profile> 25 | <Active>true</Active> 26 | </FirewallGroup> 27 | </FirewallGroups> 28 | </component> 29 | </settings> 30 | <settings pass="oobeSystem"> 31 | <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 32 | <InputLocale>en-US</InputLocale> 33 | <SystemLocale>en-US</SystemLocale> 34 | <UILanguage>en-US</UILanguage> 35 | <UILanguageFallback>en-US</UILanguageFallback> 36 | <UserLocale>en-US</UserLocale> 37 | </component> 38 | <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 39 | <AutoLogon> 40 | <Password> 41 | <Value>%ACCOUNTPASSWORD%</Value> 42 | <PlainText>false</PlainText> 43 | </Password> 44 | <Enabled>%AUTOLOGON%</Enabled> 45 | <LogonCount>%LOGONCOUNT%</LogonCount> 46 | <Username>%LOCALADMIN%</Username> 47 | </AutoLogon> 48 | <UserAccounts> 49 | <AdministratorPassword> 50 | <Value>%ADMINPASSWORD%</Value> 51 | <PlainText>false</PlainText> 52 | </AdministratorPassword> 53 | <LocalAccounts> 54 | <LocalAccount wcm:action="add"> 55 | <Password> 56 | <Value>%ACCOUNTPASSWORD%</Value> 57 | <PlainText>false</PlainText> 58 | </Password> 59 | <Description>Additional Administrator Account</Description> 60 | <DisplayName>%LOCALADMIN%</DisplayName> 61 | <Group>Administrators</Group> 62 | <Name>%LOCALADMIN%</Name> 63 | </LocalAccount> 64 | </LocalAccounts> 65 | </UserAccounts> 66 | <OOBE> 67 | <HideEULAPage>true</HideEULAPage> 68 | <SkipMachineOOBE>true</SkipMachineOOBE> 69 | </OOBE> 70 | <TimeZone>%TIMEZONE%</TimeZone> 71 | </component> 72 | </settings> 73 | <cpi:offlineImage cpi:source="catalog:d:/temp/install_windows server 2012 r2 serverdatacenter.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> 74 | </unattend> 75 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # DeployVHD 2 | README: [English](https://github.com/wellsluo/DeployVHD/blob/master/README.md) | [中文](https://github.com/wellsluo/DeployVHD/blob/master/README-CN.md) 3 | 4 | 本项目开发 PowerShell 脚本 DeployVHD.ps1,用于从 Windows Server/Windows Desktop 的 ISO/WIM 安装镜像中快速部署带有 Windows Server/Windows Desktop 操作系统的 VHD(X) 文件;或者编辑现有的 VHD(X)文件,并配置无人值守安装选项。 5 | 6 | 7 | <!-- TOC --> 8 | 9 | - [DeployVHD](#deployvhd) 10 | - [缘起](#缘起) 11 | - [目标](#目标) 12 | - [授权](#授权) 13 | - [特性](#特性) 14 | - [可支持性](#可支持性) 15 | - [VHD(X) 中的系统](#vhdx-中的系统) 16 | - [系统运行需求](#系统运行需求) 17 | - [使用方式](#使用方式) 18 | - [示例1](#示例1) 19 | - [示例2](#示例2) 20 | - [示例3](#示例3) 21 | - [示例4](#示例4) 22 | - [示例5](#示例5) 23 | - [示例6](#示例6) 24 | - [帮助](#帮助) 25 | - [依赖性](#依赖性) 26 | 27 | <!-- /TOC --> 28 | 29 | ## 缘起 30 | 31 | Windows 10 insider program 应该是吸引了成千上万用户、具有最广泛用户参与的软件验证项目了。针对 Windows Server,也有类似的项目,如针对关键企业用户的 "Continuous Customer Engagement Program" 项目。 两个项目都会定期发布产品新的ISO镜像,比如可能是每周会有。如此频繁的镜像发布,对于用户来说要跟上步伐去全新安装每次发布的不同版本的 build 是一件很耗时耗力的事情,当然也很 boring。 32 | 33 | 于是我开发了相应的 PowerShell 脚本,以帮助用户可以非常快速、并且无需用户手工参与地部署新操作系统 build 的 VHD(X) 文件,从而节省繁琐的安装任务和时间。 34 | 35 | 36 | 37 | ## 目标 38 | 39 | 敏捷且方便地将新的 Windows Server/Windows Desktop 迭代 build 部署到物理计算机或者虚拟机中,并在 Windows 首次启动的时候无需手工干预。 40 | 41 | 对于非常有热情验证 Windows Server/Windows Desktop 新鲜 build 的新特性的发烧友们,或者希望方便地生成多个版本的 Windows Server/Windows Desktop 的系统虚拟磁盘的企业 IT 管理员,这个脚本将会极大地帮助你们。 42 | 43 | 当然,这个脚本也是 "Windows Server CCEP" 和 "Windows 10 Insider" 项目参与者的好伙伴。 44 | 45 | 46 | 47 | ## 授权 48 | 此项目采用 MIT 授权协议,具体请参考 [LICENSE](https://github.com/wellsluo/DeployVHD/blob/master/LICENSE) 文件。 49 | 50 | ## 特性 51 | 52 | 通常来说,这个脚本适用于以下比较典型的场景: 53 | - 从 ISO/WIM 镜像生成新的 Windows Server/Windows Desktop 系统虚拟磁盘(MBR 或者 GUID 分区)文件作为在物理计算机或者虚拟机中使用的虚拟磁盘模板。 54 | - 将定制化的无人值守选项(通过Unattend.xml文件)应用到 VHD(X) 虚拟磁盘文件中。 55 | - 作为虚拟机的虚拟磁盘来运行虚拟机。 56 | - 在物理计算机上启用 "从 VHD 启动" 的功能(启用后,系统在30秒后自动重新启动到相应的 VHD(X) 虚拟磁盘中)。  57 | 58 | 脚本支持以下一些特性: 59 | 60 | - 从 Windows Server ISO/WIM 镜像文件产生 VHD(X) 文件。 61 | + 建立 MBR 或者 GUID 分区。 62 | + 制定 VHD(X) 文件磁盘的容量。 63 | + 选择 Windows Server/Windows Desktop ISO/WIM 镜像文件中系统的不同版本。 64 | 65 | 66 | - 系统首次启动时将根据无人值守文件中的配置进行自动设置,无需用户手工干预。可定制的无人值守配置选项包括: 67 | + 启用 Windows 防火墙规则以打开 "远程桌面" 和 "文件与打印机共享" 功能的相应端口。 68 | + 启动自动登录功能,并可以指定自动登录的次数。 69 | + 设定 Windows Server 的本地本地管理员账户的密码。 70 | + 为 Windows Desktop 系统添加额外的管理员账户并设定相应密码。 71 | + 启用 "远程桌面" 功能。 72 | + 设定系统地域信息为 en-US。 73 | + 指定计算机名称,或者使用运行脚本的本地计算机名称作为新系统计算机名称(针对在物理服务器上使用 Boot from VHD 的场景)。 74 | + 系统首次启动时略过最终用户许可协议和开箱即用的设置页面。 75 | + 默认指定系统时区为 "Pacific Standard Time" ,可以指定为 "China Standard Time"。 76 | + 设定注册用户名称和组织名称。 77 | + 可以禁用所有无人值守的选项。 78 | 79 | - 针对物理计算机: 80 | + 使用与运行脚本的计算机相同的计算机名称。 81 | + 启用从 VHD 启动的功能。 82 | + 系统 30 秒后自动重新启动到 VHD 虚拟磁盘中的系统。 83 | 84 | 85 | - 角色安装: 86 | + 在 Windows Server 版中默认安装 Hyper-V 角色。 87 | + 如果不需要 Hyper-V 角色,可以禁用自动安装角色。附注:在 Windows 10 1511 版及以上已经支持嵌套的虚拟化功能,所以即便带有 HyperV 角色的 VHD(X)用于虚拟机也是可以运行的)。 88 | + 如果不希望立刻添加系统启动项和自动重新启动,可以使用测试模式。   89 | 90 | - VHD(X) 文件既可以用在物理计算机上,也可以用在和虚拟机上。 91 | 92 | - 可以指定安装补丁文件或者驱动文件。 93 | 94 | 95 | *如果您还希望可以快速建立、管理多个相似功能的虚拟机并使用上述功能的 VHD(X) 文件的特性,比如建立一个演示的 Lab 系列虚拟机,请参考我的另一个 PowerShell 脚本项目:* **[ManageVM](https://github.com/wellsluo/ManageVM)**。 96 | 97 | 98 | **特别注意: 脚本中不会包括任何使用 Windows Server/Windows Desktop 的产品安装秘钥或者使用授权。如果必要,用户需要自己准备相应的授权来使用 Windows Server/Windows Desktop 系统,特别是在生产环境中使用。** 99 | 100 | 101 | 102 | ## 可支持性 103 | 104 | ### VHD(X) 中的系统 105 | 106 | 您可以从 ISO/WIM 镜像中生成以下操作系统的x64版本: 107 | - Windows Server 2016 108 | - Windows Server 2012 R2 109 | - Windows 10 110 | - Windows 8.1 111 | - Windows 7 (x86 only for GUID partition) 112 | 113 | 114 | ### 系统运行需求 115 | 116 | 要运行脚本,您需要以下操作系统版本和 PowerShell 版本: 117 | - Windows Server 2016 118 | - Windows Server 2012 R2 (Not for Windows Server 2016 and Windows 10 VHD file) 119 | - Windows 10 120 | - PowerShell 4 or above 121 | 122 | 123 | ## 使用方式 124 | 125 | 将所有文件复制到同一个文件夹,然后以管理员方式启动 PowerShell 控制台窗口,转到脚本的目录下,运行即可。 126 | 127 | 文件说明参考下表: 128 | 129 | 文件名称 | 描述 | 备注 130 | ------------ | ------------- | ------------ 131 | Convert-WindowsImage.ps1 | 基础脚本 | 提供从镜像文件到 VHD(X) 文件的函数 132 | DeployVHD.ps1 | 主要脚本 | 133 | Example-BootFromNewVHD.ps1 | 示例脚本 | 用于在物理机中启用 Boot from VHD 功能 134 | Example-Create-OSVHD.ps1 | 示例脚本 | 将 ISO 批量转换为 VHD(X) 模板文件 135 | unattend_amd64_Client.xml | 无人值守文件模板 | 桌面端版本 136 | unattend_amd64_Server.xml | 无人值守文件模板 | 服务器端版本 137 | 138 | 139 | 140 | ### 示例1 141 | 142 | 143 | ```PowerShell 144 |    .\Deploy-VHD.ps1 -SourcePath D:\ISO\Win2016.iso -CreateVHDTemplate 145 | ``` 146 | 147 | 此示例是从 D:\ISO\Win2016.ISO 镜像生成容量为 100GB,包含Windows Server 2016 Datacenter 版本,并且是动态扩展的 VHDX 文件作为 VHDX 文件模板,文件名为 WinServer2016.Hyper-V.100GB.GUID.VHDX (文件命名约定为 SourcePath.[Hyper-V].VHDSize.VHDPartitionStyle.VHDFormat),存放于运行脚本的当前目录, 并应用无人值守配置文件Unattend.xml。具体配置如下: 148 | 149 | - Computer Name:  在系统第一次启动时随机产生 150 | - AutoLogon: 禁用 151 | - RemoteDesktop:  启用 152 | - Firewall:       打开 153 | 154 | 155 | 156 | ### 示例2 157 | 158 | 159 | ```PowerShell 160 |    .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourcePath D:\ISO\Win2016.iso 161 | ``` 162 | 163 | 此示例是从 D:\ISO\Win2016.ISO 镜像生成容量为 100GB,包含Windows Server 2016 Datacenter 版本,并且是动态扩展的 VHDX 文件 WinServer2016.VHDX,存放于运行脚本的当前目录, 并应用无人值守配置文件Unattend.xml。具体配置如下: 164 | 165 | - Computer Name:  与运行脚本的计算机相同 166 | - AutoLogon: 禁用 167 | - RemoteDesktop:  启用 168 | - Firewall:       打开 169 | 170 | 171 | ### 示例3 172 | 173 | 174 | ```PowerShell 175 |    .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourceVHD D:\VHDX\Win2016-Template.vhdx 176 | ``` 177 | 178 | 此示例复制 D:\VHDX\Win2016-Template.vhdxand 文件为当前运行脚本目录下的 WinServer2016.VHDX 文件,并应用默认的无人值守配置文件Unattend.xml。 179 | 180 | 181 | ### 示例4 182 | 183 | 184 | ```PowerShell 185 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX 186 | ``` 187 | 188 | 此示例直接编辑当前目录下的 WinServer2016.VHDX 文件,应用默认的无人值守配置文件Unattend.xml。 189 | 190 | 191 | 192 | ### 示例5 193 | 194 | 195 | ```PowerShell 196 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -ComputerName Test-01 -AutoLogon 197 | ``` 198 | 199 | 此示例直接编辑当前目录下的 WinServer2016.VHDX 文件,设置 Computer Name 为 'Test-01',并启用 "自动登录" 功能。 200 | 201 | 202 | 203 | ### 示例6 204 | 205 | 206 | ```PowerShell 207 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -EnableNativeBoot -Restart 208 | ``` 209 | 210 | 此示例直接编辑当前目录下的 WinServer2016.VHDX 文件,并启用 "Boot from VHD" 功能,系统将在 30 秒后重新启动。计算机名称与运行脚本的计算机相同。 211 | 212 | 213 | 214 | 215 | 216 | 217 | ## 帮助 218 | 219 | 本脚本遵循 PowerShell 标准的帮助方式。可运行以下命令来获取帮助:   220 | 221 | ```PowerShell 222 | 223 | Help .\Deploy-VHD.PS1 -Detailed 224 | 225 | ``` 226 | 227 | 228 | ## 依赖性 229 | 230 | 本脚本需要调用 'Convert-WindowsImage.ps1' 中所提供的的功能。'Convert-WindowsImage.ps1' 是Microsoft 提供的示例代码,用于从 ISO/WIM 镜像生成 VHD(X) 文件。本脚本的系统生成功能本使用了其中的部分特性。 231 | 232 | 'DeployVHD' 中包含了版本为 "10.0.9000.0.fbl_core1_hyp_dev(mikekol).141224-3000.amd64fre" 的文件。 233 | 234 | 其最新信息和更新,请您参考: 235 | http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f 236 | -------------------------------------------------------------------------------- /Example-Create-OSVHDS.ps1: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Copyright 2017- Wei Luo 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | # 23 | # 24 | # SCRIPT_VERSION: 1.0.100.Main.20170218.1818.0.Release 25 | # 26 | 27 | 28 | <# 29 | .NOTES 30 | THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. 31 | THE SAMPLE SOURCE CODE IS UNDER THE MIT LICENSE. 32 | Developed by Wei Luo. 33 | 34 | .SYNOPSIS 35 | This script is used to create multiple Windows Server/Desktop OS VHD(X) 36 | at one running time from the source ISO. Software update packages will 37 | be applied if provided. 38 | 39 | It depends on the script "Deploy-VHD.ps1". It can be considered as 40 | sample script of "Deploy-VHD.ps1". 41 | 42 | .PARAMETER NewVHDPath 43 | The name and path of the Virtual Hard Disk from which is boot. 44 | 45 | .PARAMETER TestMode 46 | Boot entry will not be added and restart will not happen. 47 | 48 | .DESCRIPTION 49 | The script will create multiple VHD(X) files with different 50 | configurations and apply updates if provided. 51 | 52 | 53 | .EXAMPLE 54 | .\Create-OSVHDs.ps1 -ServerISO C:\ISO\WinServer2016.ISO -PackagePath C:\Packages 55 | 56 | This command will create DataCenter and DataCenter Core edition VHD(X) 57 | files. 58 | 59 | .EXAMPLE 60 | .\Create-OSVHDs.ps1 -ClientISO C:\ISO\Windows10.ISO -PackagePath C:\Packages -Edition Professional 61 | 62 | This command will create Prefessional edition Windows 10 VHD(X) 63 | files. 64 | 65 | .EXAMPLE 66 | .\Create-OSVHDs.ps1 -ServerISO C:\ISO\WinServer2016.ISO -ClientISO C:\ISO\Windows10.ISO -Pacages C:\Packages 67 | 68 | This command will create both Windows Server (DataCenter and Datacenter 69 | Core) and Windows 10 VHD(X) (Enterprise) files. 70 | 71 | .OUTPUTS 72 | System.IO.FileInfo 73 | 74 | #> 75 | 76 | [CmdletBinding(DefaultParametersetName="EditVHD")] 77 | Param( 78 | [Parameter(Mandatory=$False)] 79 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 80 | [string]$ServerISO, 81 | 82 | [Parameter(Mandatory=$False)] 83 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 84 | [string]$ClientISO, 85 | 86 | [Parameter(Mandatory=$False)] 87 | [ValidateScript({Test-Path $_})] 88 | [string]$PackagePath, 89 | 90 | [Parameter(Mandatory=$False)] 91 | [ValidateSet("Enterprise", "Ultimate", "Professional")] 92 | [string]$Edition 93 | ) 94 | 95 | 96 | $registerOwner = 'User' 97 | $registerOrganization = 'Organization' 98 | 99 | 100 | 101 | If($serverISO) 102 | { 103 | 104 | if($PackagePath) 105 | { 106 | #DataCenter Edition 107 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 108 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 109 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 110 | 111 | #DataCenter Core Edition 112 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -Edition ServerDataCenterCore -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 113 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -Edition ServerDataCenterCore -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 114 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -Edition ServerDataCenterCore -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Package $PackagePath -Verbose 115 | } 116 | else 117 | { 118 | #DataCenter Edition 119 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 120 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 121 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 122 | 123 | #DataCenter Core Edition 124 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -Edition ServerDataCenterCore -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 125 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -DisableHyperV -Edition ServerDataCenterCore -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 126 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ServerISO -VHDPath .\ -Edition ServerDataCenterCore -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Verbose 127 | } 128 | } 129 | 130 | 131 | If($ClientISO) 132 | { 133 | If(!$Edition) 134 | { 135 | $Edition = 'Enterprise' 136 | 137 | } 138 | if($PackagePath) 139 | { 140 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ClientISO -VHDPath .\ -DisableHyperV -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Edition $Edition -Package $PackagePath -Verbose 141 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ClientISO -VHDPath .\ -DisableHyperV -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Edition $Edition -Package $PackagePath -Verbose 142 | } 143 | else 144 | { 145 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ClientISO -VHDPath .\ -DisableHyperV -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Edition $Edition -Verbose 146 | Deploy-VHD.PS1 -CreateVHDTemplate -SourcePath $ClientISO -VHDPath .\ -DisableHyperV -MBRPartition -RegisteredOwner $registerOwner -RegisteredOrganization $registerOrganization -Edition $Edition -Verbose 147 | } 148 | } 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeployVHD 2 | README: [English](https://github.com/wellsluo/DeployVHD/blob/master/README.md) | [中文](https://github.com/wellsluo/DeployVHD/blob/master/README-CN.md) 3 | 4 | PowerShell script to deploy new VHD(X) file from Windows Server/Desktop image file ISO/WIM, or edit existed VHD(X) file, and configure un-attend information. 5 | 6 | 7 | <!-- TOC --> 8 | 9 | - [DeployVHD](#deployvhd) 10 | - [Motivation](#motivation) 11 | - [Objectives](#objectives) 12 | - [License](#license) 13 | - [Features](#features) 14 | - [Supportability](#supportability) 15 | - [VHD(X) OS](#vhdx-os) 16 | - [System Requirements](#system-requirements) 17 | - [Usage](#usage) 18 | - [EXAMPLE1](#example1) 19 | - [EXAMPLE2](#example2) 20 | - [EXAMPLE3](#example3) 21 | - [EXAMPLE4](#example4) 22 | - [EXAMPLE5](#example5) 23 | - [EXAMPLE6](#example6) 24 | - [Help](#help) 25 | - [Dependency](#dependency) 26 | 27 | <!-- /TOC --> 28 | 29 | ## Motivation 30 | 31 | 32 | "Windows 10 Insider" program should be most popular program which attracts thousands of participants. As Windows Server, there is similar program like "Continuous Customer Engagement Program" for enterprise IT administrators. Both programs will issue new product build ISO periodically, like weekly. For each build, it will be time consuming for user to setup Windows Server/Windows Desktop from scratch, and so does for different editions. 33 | 34 | So I created the project to develop the PowerShell script, which can help user to deploy the new build to VHD(X) file very quickly and with un-attend features to lower down user tasks and time. 35 | 36 | 37 | 38 | ## Objectives 39 | 40 | 41 | Agile and easy way to deploy VHD to physical server (NativeBoot) or VM from Windows Server/Desktop iteration builds with un-attend configurations. 42 | 43 | For a passionate user who frequently validates the Windows Server/Windows Desktop fresh builds, or IT administrator who needs to generate virtual disks with multiple editions of Windows Server/Windows Desktop, this script will help you out. 44 | 45 | The script is also a pal of "Windows Server CCEP" program and "Windows 10 Insider" program participants. 46 | 47 | 48 | ## License 49 | 50 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/wellsluo/DeployVHD/blob/master/LICENSE) file for details. 51 | 52 | ## Features 53 | 54 | 55 | Generally, the script works in following scenarios: 56 | - Generate new VHD(X) file (MBR or GPT partition) and apply Windows Server OS from ISO/WIM as template file for both virtual machine and physical machine (boot from VHD). 57 | - Customize un-attend options (into Unattend.xml), and apply it to VHD(X). 58 | - Use the VHD(X) file as system virtual disk in a virtual machine. 59 | - Enable boot from VHD (NativeBoot) in physical server (auto restart if enabled). 60 | 61 | So the following features are supported: 62 | 63 | 64 | - Generate VHD(X) file from Windows Server ISO/WIM image file. 65 | + Create MBR or GUID partition of VHD(X) files. 66 | + Set VHD(X) file size . 67 | + Select different Edition of Windows Server/Windows Desktop. 68 | 69 | 70 | - On the first boot, OS will setup automatically without user actions. Customize following un-attend configurations to the VHD(X) files: 71 | + Enable Windows Firewall rules of "Remote Desktop" and "File and Printer Sharing". 72 | + Enable automatic logon after Windows boots up, and set automatic logon count. 73 | + Set local administrator password for Windows Server editions. 74 | + Add additional local administrator account and set its password for Windows Desktop editions. 75 | + Enable "Remote Desktop" option. 76 | + Set system locale to en-US. 77 | + Set computer name or use same computer name which runs the script (for creating native boot VHD of physical machine). 78 | + Hide EULA and OOBE pages on first booting up. 79 | + Set time zone to "Pacific Standard Time" by default, or set to "China Standard Time". 80 | + Set register owner and organization of Windows. 81 | + Un-attend options can be disabled if not necessary. 82 | 83 | - Physical Machine: 84 | + Use same computer name as current OS. 85 | + Enable NativeBoot 86 | + Restart in 30 seconds after enabling NativeBoot 87 | 88 | 89 | - Role installed: 90 | + Install Hyper-V role for Windows Server by default. 91 | + It can be disabled if Hyper-V server is not used or for virtual machines (Windows 10 1511 or above supports nested virtualization, Hyper-V role can run in virtual machine). 92 | + Provides test mode if you don't want to add the boot entry and restart physical machine. 93 | 94 | - VHD(X) file can be used on both physical machine and Hyper-V virtual machine. 95 | 96 | - Apply update packages or drivers when generating the VHD(X) files. 97 | 98 | 99 | *If you also need features that to create multiple similar virtual machines very quickly, such as a demo lab, it is proviced in other PowerShell project. Please refer to* **[ManageVM](https://github.com/wellsluo/ManageVM)**. 100 | 101 | 102 | **Note: No any Windows Server/Windows Desktop product keys or licenses are included. User needs to have your own licenses to use Windows Server/Windows Desktop in production environment.** 103 | 104 | 105 | 106 | ## Supportability 107 | ### VHD(X) OS 108 | 109 | You can generate the VHD(X) files from ISO with following OS versions, x64 only: 110 | - Windows Server 2016 111 | - Windows Server 2012 R2 112 | - Windows 10 113 | - Windows 8.1 114 | - Windows 7 (only for MBR partition, no GUID partition for virtual machine) 115 | 116 | 117 | ### System Requirements 118 | 119 | You can run the script on following OS versions and PowerShell version: 120 | - Windows Server 2016 121 | - Windows Server 2012 R2 (Not for Windows Server 2016 and Windows 10 VHD file) 122 | - Windows 10 123 | - PowerShell 4 or above 124 | 125 | 126 | ## Usage 127 | 128 | Put the script and all other files under a folder. Run PowerShell command window in "Elevated" mode. Then go to the folder to run it. 129 | 130 | File list: 131 | 132 | Name | Description | Memo 133 | ------------ | ------------- | ------------ 134 | Convert-WindowsImage.ps1 | Dependency | Provide function to generate VHD(X) from ISO/WIM image 135 | DeployVHD.ps1 | Main script | 136 | Example-BootFromNewVHD.ps1 | Example script | Enable boot from vhd in physical machine 137 | Example-Create-OSVHD.ps1 | Example script | Generate multiple VHD(X) files from ISO at one running time 138 | unattend_amd64_Client.xml | Un-attend template | for Windows Desktop 139 | unattend_amd64_Server.xml | Un-attend template | for Windows Server 140 | 141 | 142 | 143 | 144 | ### EXAMPLE1 145 | 146 | ```PowerShell 147 |    .\Deploy-VHD.ps1 -SourcePath D:\ISO\Win2016.iso -CreateVHDTemplate 148 | 149 | ``` 150 | 151 | This command will create a 100GB dynamically-expanding VHDX containing the Datacenter SKU, and will be named as "WinServer2016.Hyper-V.100GB.GUID.VHDX" (name convension is "SourcePath.[Hyper-V].VHDSize.VHDPartitionStyle.VHDFormat"). Computer name will be generated randomly on first booting up. Default un-attend configurations are applied as following: 152 | 153 | - Computer Name: Random name on first booting 154 | - AutoLogon: Disabled 155 | - RemoteDesktop: Enabled 156 | - Firewall: Opened 157 | 158 | 159 | ### EXAMPLE2 160 | 161 | 162 | ```PowerShell 163 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourcePath D:\ISO\Win2016.iso 164 | ``` 165 | 166 | This command will create a dynamically-expanding 100GB VHDX containing the Datacenter SKU, and will be named WinServer2016.vhdx 167 | Unattend.xml will be applied with default configurations: 168 | 169 | - Computer Name: Same as host 170 | - AutoLogon: Disabled 171 | - RemoteDesktop: Enabled 172 | - Firewall: Opened 173 | 174 | 175 | 176 | ### EXAMPLE3 177 | 178 | 179 | ```PowerShell 180 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourceVHD D:\VHDX\Win2016-Template.vhdx 181 | ``` 182 | 183 | This command will use VHDX file D:\VHDX\Win2016-Template.vhdx and copy as WinServer2016.VHDX. 184 | Unattend.xml will be applied with default configurations. 185 | 186 | 187 | 188 | ### EXAMPLE4 189 | 190 | 191 | ```PowerShell 192 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX 193 | ``` 194 | 195 | This command will edit WinServer2016.VHDX directly with default un-attend configurations. 196 | 197 | 198 | 199 | ### EXAMPLE5 200 | 201 | 202 | ```PowerShell 203 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -ComputerName Test-01 -AutoLogon 204 | ``` 205 | 206 | This command will edit WinServer2016.VHDX, set the computer name to 'Test-01', and enable Autologon. 207 | 208 | 209 | 210 | ### EXAMPLE6 211 | 212 | 213 | ```PowerShell 214 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -EnableNativeBoot -Restart 215 | ``` 216 | 217 | This command will edit WinServer2016.VHDX file, and enable boot from VHD, then system will restart in 30 seconds. 218 | 219 | 220 | 221 | ## Help 222 | 223 | The script aligns with standard PowerShell help format. To get the help of the script, just run command: 224 | 225 | ```PowerShell 226 | 227 | Help .\Deploy-VHD.PS1 -Detailed 228 | 229 | ``` 230 | 231 | 232 | ## Dependency 233 | 234 | The script depends on the script 'Convert-WindowsImage.ps1' from Microsoft. The script 'Convert-WindowsImage.ps1' provides basic functions to convert ISO or WIM image to VHD(X) files. 235 | 236 | 'DeployVHD' includes version "10.0.9000.0.fbl_core1_hyp_dev(mikekol).141224-3000.amd64fre". 237 | 238 | Latest version can be found at: 239 | http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f 240 | -------------------------------------------------------------------------------- /Deploy-VHD.PS1: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017- Wei Luo 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | # 23 | # SCRIPT_VERSION: 2.0.100.Main.20170218.1015.0.Release 24 | # 25 | 26 | 27 | <# 28 | .NOTES 29 | THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. 30 | THE SAMPLE SOURCE CODE IS UNDER THE MIT LICENSE. 31 | Developed by Wei Luo. 32 | 33 | .SYNOPSIS 34 | Creates a bootable VHD(X) based on Windows Server/Cllient installation 35 | media image (ISO/WIM). 36 | 37 | .DESCRIPTION 38 | Creates a bootable VHD(X) based on Windows OS installation media image 39 | (ISO/WIM). 40 | 41 | Script to: 42 | 1. Generate VHDX file from Windows Server ISO/WIM file. 43 | 2. Customize settings with Unattend.xml file and apply to VHDX 44 | file. 45 | 3. Enable NativeBoot 46 | 4. Enable AutoLogon. Enabled by default for Windows Desktop. 47 | 5. Create MBR partition of VHDX 48 | 6. Hyper-V role and management tools are installed in VHD(X) 49 | by default for Windows Server. 50 | 51 | Note: this feature needs the Dism servicing version is same or 52 | higherr thn the target OS. Which means it doesn't support 53 | this feature in Windows Server 2012 R2 to generate Windows 54 | Server 2016 VHD(X) file with Hyper-V role. 55 | 56 | Script can be run at: 57 | Windows Server 2016 58 | Windows Server 2012 R2 (create Windows Server 2012 R2 image only) 59 | Windows 10 x64 60 | 61 | The following OS image supported: 62 | Windows Server 2016 63 | Windows Server 2012 R2 64 | *Windows 10 x64 65 | *Windows 8.1 x64 66 | *Windows 8 x64 67 | *windows 7 x64 (MBR partition only for virtual machine) 68 | 69 | .PARAMETER VHDPath 70 | The name and path of the Virtual Hard Disk to create or edit. The 71 | parameter is must have when creating, editing, copying VHD(X) file, 72 | and should be the file name with parent path. 73 | 74 | When creating the VHD(X) file as template (with "CreateVHDTemplate" 75 | switch), it should be the parnet path, not the file name. Omitting 76 | this parameter will create the VHD(X) in the current directory, and 77 | will automatically name the file in the following format: 78 | 79 | SourcePath.[Hyper-V].VHDSize.VHDPartitionStyle.VHDFormat 80 | 81 | .PARAMETER SourcePath 82 | The complete path to the WIM or ISO file that will be converted to a 83 | Virtual Hard Disk. The ISO file must be valid Windows installation 84 | media to be recognized successfully. And the product type of ISO file 85 | will be checked. 86 | 87 | "WinNT" for Windwos Desktop. 88 | "ServerNT" for Windows Server. 89 | 90 | .PARAMETER CreateVHDTemplate 91 | Set the VHD(X) file as template file. Source image file name will be 92 | used to generate template VHD(X) file name, with Hyper-V features, 93 | partition type and disk size. 94 | 95 | .PARAMETER DisableUnattend 96 | Unattend.xml file will not be insert to the VHD(X) file. Used when 97 | "CreateVHDTemplate" is enabled. 98 | 99 | .PARAMETER Edition 100 | The name or image index of the image to apply from the WIM. Detault 101 | value is "ServerDatacenter" for Server OS, "Enterprise" for client OS. 102 | 103 | .PARAMETER IsDesktop 104 | If the OS is Windows Desktop, enable this switch to do: 105 | //- Enable "AutoLogon" to enable local administrator account. 106 | - Disable Hyper-V feature installation. 107 | - Set Computer Name to default computer name when using "CreateVHDTemplate" 108 | switch. 109 | 110 | .PARAMETER SourceVHD 111 | The complete path to the VHD(X) template file that will be copied to a 112 | Virtual Hard Disk. Used only in "Copy" mode. 113 | 114 | .PARAMETER VHDSize 115 | The size of the Virtual Hard Disk to create. Size range is 15GB-64TB. 116 | The default value is 100GB. 117 | 118 | .PARAMETER VHDFormat 119 | Specifies whether to create a VHD or VHDX formatted Virtual Hard Disk. 120 | The default is VHD. 121 | 122 | .PARAMETER ComputerName 123 | Unattend setting. Set the computer name for the system inside VHD(X) file. 124 | If this parameter is not provided, the local computer name will be used. 125 | This is typical sceanrio when creating a new VHD(X) file for local 126 | machine to enable "boot from VHD". 127 | 128 | .PARAMETER LocalAdminAccount 129 | Unattend setting. Set the additional local admin account for Windows 130 | Desktop (with "IsDesktop" switch). 131 | 132 | It is ignored with Windows Server OS. 133 | 134 | .PARAMETER AdminPassword 135 | Unattend setting. 136 | 1. Set the default local administrator account password. 137 | 2. Set the additional local admin account password for Windows Desktop 138 | (with "IsDesktop" switch). 139 | 140 | .PARAMETER EnableAutoLogon 141 | Unattend setting. Enable "AutoLogon" feature in Unattend.xml. 142 | 143 | .PARAMETER AutoLogonCount 144 | Unattend setting. If "AutoLogon" feature is enabled, then set the autlogon 145 | count in Unattend.xml. Default value is 1. 146 | 147 | .PARAMETER DisableHyperV 148 | Setting for create new VHD(X) file from ISO. By default, Hyper-V role 149 | and management tools will be installed in to VHD(X) file. By using this swith, 150 | Hyper-V role will not be installed to VHD(X) file. 151 | 152 | .PARAMETER MBRPartition 153 | Create the MBR partition when creating VHD(X) file. By default, GPT partition 154 | will be created. 155 | 156 | .PARAMETER Driver 157 | Full path to driver(s) (.inf files) to install to the OS inside the VHD(x). 158 | 159 | .PARAMETER Package 160 | Install specified Windows Package(s). Accepts path to either a directory 161 | or individual CAB or MSU file. 162 | 163 | .PARAMETER EnableNativeBoot 164 | Set the BCD entry for the VHD(X) file in local host. VHDPath cannot be 165 | network path. 166 | 167 | .PARAMETER Restart 168 | Restart local host in 30 seconds. Must work with "-EnableNativeBoot". 169 | 170 | .EXAMPLE 171 | .\Deploy-VHD.ps1 -SourcePath D:\ISO\Win2016.iso -CreateVHDTemplate 172 | 173 | This command will create a dynamically-expanding 100GB VHDX file as 174 | template, containing the Windows Server 2016 Datacenter SKU from image 175 | file D:\ISO\Win2016.iso. The template file will be named as 176 | "WinServer2016.Hyper-V.100GB.GUID.VHDX". 177 | 178 | Unattend.xml will be applied with default settings: 179 | Computer Name: Random name on first booting 180 | AutoLogon: Disabled 181 | RemoteDesktop: Enabled 182 | Firewall: Opened 183 | 184 | 185 | .EXAMPLE 186 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourcePath 187 | D:\WIM\install.wim 188 | 189 | This command will create a 100GB dynamically expanding VHDX in the 190 | current folder. 191 | 192 | The VHDX will be based on the Datacenter edition from 193 | D:\WIM\install.wim, and will be named WinServer2016.VHDX. 194 | 195 | Unattend.xml will be applied with default settings: 196 | Computer Name: Same as host 197 | AutoLogon: Disabled 198 | RemoteDesktop: Enabled 199 | Firewall: Opened 200 | 201 | .EXAMPLE 202 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourcePath D:\ISO\Win2016.iso 203 | 204 | This command will create a dynamically-expanding 100GB VHDX containing 205 | the Windows Server 2016 Datacenter SKU, and will be named as 206 | WinServer2016.vhdx. 207 | 208 | Unattend.xml will be applied with default settings: 209 | Computer Name: Same as host 210 | AutoLogon: Disabled 211 | RemoteDesktop: Enabled 212 | Firewall: Opened 213 | 214 | .EXAMPLE 215 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -SourceVHD D:\VHD\Win2016-Template.vhdx 216 | 217 | This command will copy VHDX file D:\VHD\Win2016-Template.vhdx as 218 | WinServer2016.VHDX. 219 | 220 | Unattend.xml will be applied with default settings: 221 | Computer Name: Same as host 222 | AutoLogon: Disabled 223 | RemoteDesktop: Enabled 224 | Firewall: Opened 225 | 226 | .EXAMPLE 227 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX 228 | 229 | This command will edit WinServer2016.VHDX directly. 230 | 231 | Unattend.xml will be applied with default settings: 232 | Computer Name: Same as host 233 | AutoLogon: Disabled 234 | RemoteDesktop: Enabled 235 | Firewall: Opened 236 | 237 | .EXAMPLE 238 | .\Deploy-VHD.ps1 -VHDPath .\WinServer2016.VHDX -ComputerName Test-01 -AutoLogon 239 | 240 | This command will edit WinServer2016.VHDX directly, set the computer 241 | name to "Test-01". 242 | 243 | Unattend.xml will be applied with following settings: 244 | Computer Name: Test-01 245 | AutoLogon: Enabled 246 | RemoteDesktop: Enabled 247 | Firewall: Opened 248 | 249 | .EXAMPLE 250 | .\Deploy-VHD.ps1 -VHDPath WinServer2016.VHDX -EnableNativeBoot -Restart 251 | 252 | This command will edit WinServer2016.VHDX directly, and enable boot from 253 | VHD. 254 | 255 | Unattend.xml will be applied with default settings: 256 | Computer Name: Same as host 257 | AutoLogon: Disabled 258 | RemoteDesktop: Enabled 259 | Firewall: Opened 260 | 261 | System restarts in 30 seconds. 262 | 263 | .LINK 264 | https://github.com/wellsluo/DeployVHD 265 | 266 | #> 267 | 268 | 269 | 270 | [CmdletBinding(DefaultParametersetName="Info")] 271 | Param( 272 | 273 | [Parameter(Mandatory=$False,ParameterSetName='Info')] 274 | [switch]$GetVersion, 275 | 276 | [Parameter(Mandatory=$False,ParameterSetName='Info')] 277 | [switch]$GetUsage, 278 | 279 | [Parameter(Mandatory=$True,ParameterSetName='EditVHD')] 280 | [Parameter(Mandatory=$True,ParameterSetName='NewVHD')] 281 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 282 | [Parameter(Mandatory=$True,ParameterSetName='CopyVHD')] 283 | [string]$VHDPath=$PSScriptRoot, 284 | 285 | [Parameter(Mandatory=$True, ParameterSetName='NewVHD')] 286 | [Parameter(Mandatory=$True, ParameterSetName='NewVHDTemplate')] 287 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 288 | [string]$SourcePath, 289 | 290 | [Parameter(Mandatory=$True, ParameterSetName='NewVHDTemplate')] 291 | [switch]$CreateVHDTemplate, 292 | 293 | [Parameter(Mandatory=$false, ParameterSetName='NewVHDTemplate')] 294 | [switch]$DisableUnattend, 295 | 296 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 297 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 298 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 299 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 300 | [switch]$IsDesktop, 301 | 302 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 303 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 304 | [ValidateSet("ServerDataCenter","ServerDataCenterCore", "ServerDataCenterNano", ` 305 | "ServerStandard", "ServerStandardCore", "ServerStandardNano",` 306 | "Enterprise", "Professional","Ultimate")] 307 | [string]$Edition="ServerDataCenter", 308 | 309 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 310 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 311 | [ValidateNotNullOrEmpty()] 312 | [ValidateRange(15GB, 64TB)] 313 | [UInt64]$VHDSize=100GB, 314 | 315 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 316 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 317 | [ValidateSet("VHDX","VHD")] 318 | [string]$VHDFormat="VHDX", 319 | 320 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 321 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 322 | [Alias("MBR")] 323 | [switch]$MBRPartition, 324 | 325 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 326 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 327 | [switch]$DisableHyperV, 328 | 329 | [Parameter(Mandatory=$True, ParameterSetName='CopyVHD')] 330 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 331 | [string]$SourceVHD, 332 | 333 | [Parameter(Mandatory=$False, ParameterSetName='EditVHD')] 334 | [switch]$Edit, 335 | 336 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 337 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 338 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 339 | #$env:COMPUTERNAME only gives the computer name up to 15 characters (NetBIOS name limit). 340 | #So stop to use it and use function to get local host name if $ComputerName is not provided from parameter. 341 | #[string]$ComputerName=$env:COMPUTERNAME, 342 | [string]$ComputerName, 343 | 344 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 345 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 346 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 347 | [string]$LocalAdminAccount="Ladmin", 348 | 349 | [Parameter(Mandatory=$False, ParameterSetName='NewVHD')] 350 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 351 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 352 | [string]$AdminPassword='Local@123', 353 | 354 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 355 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 356 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 357 | [switch]$EnableAutoLogon, 358 | 359 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 360 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 361 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 362 | [UInt32]$AutoLogonCount=1, 363 | 364 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 365 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 366 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 367 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 368 | [string]$RegisteredOwner="User", 369 | 370 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 371 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 372 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 373 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 374 | [string]$RegisteredOrganization="Organization", 375 | 376 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 377 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 378 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 379 | [switch]$ChinaTime, 380 | 381 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 382 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 383 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 384 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 385 | [ValidateNotNullOrEmpty()] 386 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 387 | [string[]]$Driver, 388 | 389 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 390 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 391 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 392 | [Parameter(Mandatory=$False, ParameterSetName='NewVHDTemplate')] 393 | [ValidateNotNullOrEmpty()] 394 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 395 | [string[]]$Package, 396 | 397 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 398 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 399 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 400 | [switch]$EnableNativeBoot, 401 | 402 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 403 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 404 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 405 | [switch]$Restart, 406 | 407 | [Parameter(Mandatory=$False,ParameterSetName='NewVHD')] 408 | [Parameter(Mandatory=$False,ParameterSetName='EditVHD')] 409 | [Parameter(Mandatory=$False,ParameterSetName='CopyVHD')] 410 | [switch]$TestMode 411 | ) 412 | 413 | ########################################################################################## 414 | 415 | #write script message 416 | Function Write-Message 417 | { 418 | [CmdletBinding()] 419 | param( 420 | [Parameter(Mandatory = $False, ValueFromPipeline = $false)] 421 | [ValidateSet("Info","Warn", "Err", "Trace", "Debug", "TEXT")] 422 | #[ValidateNotNullOrEmpty()] 423 | [string]$category='INFO', 424 | 425 | [Parameter(Mandatory = $False, ValueFromPipeline = $false)] 426 | [string] 427 | [ValidateNotNullOrEmpty()] 428 | $text 429 | ) 430 | 431 | $CurrentTime = Get-Date -UFormat '%H:%M:%S' 432 | 433 | Switch ($category.ToUpper()) 434 | { 435 | 'INFO' 436 | { 437 | If ( $text ) 438 | { 439 | $text = "$CurrentTime INFO : $text" 440 | Write-Output $text #-ForegroundColor White 441 | } 442 | Else 443 | { 444 | Write-Output "" 445 | } 446 | } 447 | 448 | 'TEXT' 449 | { 450 | Write-Output $text 451 | } 452 | 453 | 'WARN' 454 | { 455 | $text = "$CurrentTime WARN : $text" 456 | Write-Warning $text #-ForegroundColor Yellow 457 | } 458 | 459 | 'ERR' 460 | { 461 | $text = "$CurrentTime ERROR : $text" 462 | #Write-Host $text -ForegroundColor Red 463 | Write-Error $text 464 | } 465 | 466 | 'TRACE' 467 | { 468 | Write-Verbose "$CurrentTime TRACE : $text" 469 | } 470 | 471 | 'DEBUG' 472 | { 473 | Write-Debug "$CurrentTime DEBUG : $text" 474 | } 475 | 476 | Default 477 | { 478 | Write-Output "" 479 | } 480 | } 481 | } 482 | 483 | 484 | #Start PowerShell Console in elevated mode. 485 | Function Start-PSConsoleAsAdmin 486 | { 487 | Start-Process powershell.exe "-NoExit -ExecutionPolicy Bypass" -Verb RunAs 488 | } 489 | 490 | #check if PowerShell console is running at Elevated mode. 491 | Function Test-PSConsoleRunAsAdmin 492 | { 493 | return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 494 | } 495 | 496 | 497 | #Base64 encode 498 | function ConvertTo-Base64 ($plainString) { 499 | $encodedString = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($plainString)) 500 | return $encodedString 501 | } 502 | <# 503 | #Base64 decode 504 | function ConvertFrom-Base64($encodedString) { 505 | $decodedString = [System.Text.Encoding]::UNICODE.GetString([System.Convert]::FromBase64String($encodedString)) 506 | return $decodedString ; 507 | } 508 | #> 509 | 510 | #Get local host name 511 | Function Get-LocalHostName 512 | { 513 | return [System.Net.Dns]::GetHostName() 514 | 515 | } 516 | 517 | 518 | #process the information of Unattend.xml file. 519 | function ProcessUnattend 520 | { 521 | [CmdletBinding()] 522 | param 523 | ( 524 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 525 | [ValidateNotNullOrEmpty()] 526 | [string]$WorkSpaceName, 527 | 528 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 529 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 530 | [string]$VHDPath, 531 | 532 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 533 | [string]$UnattendTemplate, 534 | 535 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 536 | [string]$EnableAutoLogon, 537 | 538 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 539 | [string]$AutologonCount, 540 | 541 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 542 | [string]$LocalAdminAccount, 543 | 544 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 545 | [string]$AdminPassword, 546 | 547 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 548 | [string]$ComputerName, 549 | 550 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 551 | [string]$RegisteredOrganization, 552 | 553 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 554 | [string]$RegisteredOwner, 555 | 556 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 557 | [string]$TimeZone, 558 | 559 | [Parameter(Mandatory = $False, ValueFromPipeline = $false)] 560 | [ValidateNotNullOrEmpty()] 561 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 562 | [string[]]$Driver, 563 | 564 | [Parameter(Mandatory = $False, ValueFromPipeline = $false)] 565 | [ValidateNotNullOrEmpty()] 566 | [ValidateScript({ Test-Path $(Resolve-Path $_) })] 567 | [string[]]$Package 568 | 569 | ) 570 | 571 | ############################################################################################### 572 | # prepare unattend file content. 573 | 574 | Write-Message "Debug" ("Enter Function: ProcessUnattend") 575 | 576 | #Image Mount directory 577 | Set-Variable -Name MountSubFolder -Value "MountDir" -Option Constant 578 | 579 | #Unattend.xml file put path 580 | Set-Variable -Name UnattendWindowsFolder -Value ("Windows\panther") -Option Constant 581 | Set-Variable -Name UnattendFileName -Value "Unattend.xml" -Option Constant 582 | 583 | #unattend keyword 584 | Set-Variable -Name AutoLogonKeyword -Value "%AUTOLOGON%" -Option Constant 585 | Set-Variable -Name AutoLogonCountKeyword -Value "%LOGONCOUNT%" -Option Constant 586 | Set-Variable -Name ComputerNameKeyword -Value "%COMPUTERNAME%" -Option Constant 587 | Set-Variable -Name RegisteredOwnerKeyword -Value "%REGISTEREDOWNER%" -Option Constant 588 | Set-Variable -Name RegisteredOrganizationKeyword -Value "%REGISTEREDORGANIZATION%" -Option Constant 589 | Set-Variable -Name TimeZoneKeyword -Value "%TIMEZONE%" -Option Constant 590 | 591 | #default administrator password keyword 592 | Set-Variable -Name AdminPasswordKeyword -Value "%ADMINPASSWORD%" -Option Constant 593 | #Client, additional local admin account name 594 | Set-Variable -Name LocalAdminAccountKeyword -Value "%LOCALADMIN%" -Option Constant 595 | #Autologon account/additional admin account (for Windows client) password 596 | Set-Variable -Name AccountPasswordKeyword -Value "%ACCOUNTPASSWORD%" -Option Constant 597 | 598 | 599 | #Create Unattendworkspace to support multiple instances 600 | <# 601 | try { 602 | $WorkSpaceName = New-Guid #Only works in PowerShell version 5 and above. 603 | } 604 | catch { 605 | $WorkSpaceName = [Guid]::NewGuid().ToString() 606 | } 607 | 608 | #> 609 | $UnattendWorkSpace = Join-Path $PSScriptRoot $WorkSpaceName 610 | mkdir $UnattendWorkSpace -Force | Out-Null 611 | 612 | #Write trace information 613 | Write-Message "Trace" ("VHD(X) name: $($VHDPath)") 614 | Write-Message "Trace" ("Computer name: $($ComputerName)") 615 | Write-Message "Trace" ("Unattend file target path: $($UnattendWindowsFolder)") 616 | Write-Message "Trace" ("Unattend template: $($UnattendTemplate)") 617 | Write-Message "Trace" ("Enable auto logon: $($EnableAutoLogon.ToString())") 618 | Write-Message "Trace" ("Autologon count: $($AutologonCount)") 619 | Write-Message "Trace" ("Additional local admin account: $($LocalAdminAccount)") 620 | Write-Message "Trace" ("Registered organization: $($RegisteredOrganization)") 621 | Write-Message "Trace" ("Registered owner: $($RegisteredOwner)") 622 | Write-Message "Trace" ("Time Zone: $($TimeZone)") 623 | 624 | #Generate unattend.xml file and put the computer name 625 | $UnattendFile = Join-Path $UnattendWorkSpace $UnattendFileName 626 | Write-Message "Trace" ("Unattend file: $($UnattendFile)") 627 | Write-Message "Info" ("Generating the $($UnattendFileName) file ...") 628 | 629 | #remove the old one if existing. 630 | Remove-Item -Path $UnattendFile -Force -ErrorAction SilentlyContinue 631 | 632 | #Check if the unattend template existes 633 | If(!(Test-Path $UnattendTemplate)) 634 | { 635 | Write-Message "ERR" "Error: $($UnattendTemplate) file doesn't exist." 636 | Exit -1 637 | } 638 | 639 | 640 | $UnattendContent = Get-Content -Path $UnattendTemplate 641 | #write the computer name to Unattend file. 642 | $UnattendContent = Foreach-Object {$UnattendContent -replace $ComputerNameKeyword, $ComputerName} 643 | #write AutoLogon option 644 | $UnattendContent = Foreach-Object {$UnattendContent -replace $AutoLogonKeyword, $($EnableAutoLogon.ToString().ToLower())} # $AutoLogonValue} 645 | $UnattendContent = Foreach-Object {$UnattendContent -replace $AutoLogonCountKeyword, $AutoLogonCount} 646 | #Write account information 647 | $UnattendContent = Foreach-Object {$UnattendContent -replace $LocalAdminAccountKeyword, $LocalAdminAccount} 648 | #write registration 649 | $UnattendContent = Foreach-Object {$UnattendContent -replace $RegisteredOwnerKeyword, $RegisteredOwner} 650 | $UnattendContent = Foreach-Object {$UnattendContent -replace $RegisteredOrganizationKeyword, $RegisteredOrganization} 651 | #time zone 652 | $UnattendContent = Foreach-Object {$UnattendContent -replace $TimeZoneKeyword, $TimeZone} 653 | #Password 654 | $encryptedAdminPwd = ConvertTo-Base64("$($AdminPassword)Password") 655 | $encryptedDefaultAdminAccountPwd = ConvertTo-Base64("$($AdminPassword)AdministratorPassword") 656 | $UnattendContent = Foreach-Object {$UnattendContent -replace $AdminPasswordKeyword, $encryptedDefaultAdminAccountPwd} 657 | $UnattendContent = Foreach-Object {$UnattendContent -replace $AccountPasswordKeyword, $encryptedAdminPwd} 658 | 659 | #Write Unattend.xml file 660 | Set-Content -Path $UnattendFile -Force -Value $UnattendContent 661 | 662 | Write-Message "Debug" "$($UnattendContent)" 663 | ############################################################################################### 664 | # Put unattend file. 665 | # 666 | 667 | #Mount VHD file 668 | Write-Message "Info" ("Mount VHD file $($VHDPath) and put file $($UnattendFileName).") 669 | $MountFolder = Join-Path $UnattendWorkSpace $MountSubFolder 670 | If (Test-Path $MountFolder) 671 | { 672 | Write-Message "Trace" ("Clean mount folder $($MountFolder).") 673 | Remove-Item ("$MountFolder\*") -Force -Recurse -ErrorAction Stop #mount dir is not empty and cannot be removed 674 | } 675 | else 676 | { 677 | Write-Message "Trace" ("Mount folder $($MountFolder) doesn't exist, create it.") 678 | mkdir $MountFolder -Force | Out-Null 679 | } 680 | 681 | Write-Message "Trace" ("Mounting VHD $($VHDPath) to folder $($MountFolder) ...") 682 | Mount-WindowsImage -ImagePath $VHDPath -Index 1 -Path $MountFolder | Out-Null 683 | 684 | $UnattendFileTargetPath = Join-Path $MountFolder $UnattendWindowsFolder 685 | mkdir $UnattendFileTargetPath -force -ErrorAction SilentlyContinue | Out-Null 686 | copy-item $UnattendFile $UnattendFileTargetPath -Force 687 | 688 | #add drivers 689 | if ( $Driver ) { 690 | 691 | Write-Message "Info" "Adding Drivers to the image..." 692 | 693 | $Driver | ForEach-Object -Process { 694 | 695 | Write-Message "Trace" "Driver path: $PSItem" 696 | $Dism = Add-WindowsDriver -Path $MountFolder -Recurse -Driver $PSItem 697 | } 698 | } 699 | 700 | #add .CAB or .MSU packages 701 | if ( $Package ) { 702 | Write-Message "Info" "Adding Windows Packages to the image..." 703 | $Package | ForEach-Object -Process { 704 | Write-Message "Trace" "Package path: $PSItem" 705 | $Dism = Add-WindowsPackage -Path $MountFolder -PackagePath $PSItem 706 | } 707 | } 708 | 709 | Write-Message "Trace" ("Dismounting $($VHDPath) and commit change.") 710 | Dismount-WindowsImage -Path $MountFolder -Save | Out-Null 711 | 712 | #clean up 713 | Start-Sleep 1 714 | #RD $MountFolder -Force -Recurse | Out-Null 715 | #Del $UnattendFile -Force | Out-Null 716 | Remove-Item $UnattendWorkSpace -Force -Recurse | Out-Null 717 | } 718 | 719 | 720 | #get machine manufacturer and model from WMI 721 | Function Get-MachineMM 722 | { 723 | try 724 | { 725 | $property = Get-WmiObject -Class Win32_ComputerSystem -ComputerName . -ErrorAction Stop ` 726 | |Select-Object -Property Manufacturer,Model 727 | } 728 | catch 729 | { 730 | Write-Message "Err" "$($Error[0])" 731 | $model = $null 732 | } 733 | 734 | return $property 735 | } 736 | 737 | 738 | Function IsVirtualMachine 739 | { 740 | Param 741 | ( 742 | [Parameter(Mandatory=$False)] 743 | [string]$model 744 | ) 745 | 746 | If (! $model) 747 | { 748 | $model = (Get-MachineMM).Model 749 | } 750 | 751 | $bVirtualMachine = $False 752 | If($model) 753 | { 754 | $bVirtualMachine = $model.ToUpper().Contains("VIRTUAL") 755 | } 756 | 757 | Return $bVirtualMachine 758 | } 759 | 760 | #Enable the boot from VHD feature 761 | function EnableNativeBoot 762 | { 763 | [CmdletBinding()] 764 | param 765 | ( 766 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 767 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 768 | [string]$VHDPath, 769 | 770 | [Parameter(Mandatory = $false, ValueFromPipeline = $false)] 771 | [switch]$Restart, 772 | 773 | [Parameter(Mandatory = $false, ValueFromPipeline = $false)] 774 | [switch]$TestMode 775 | ) 776 | 777 | #check if script is running at physical machine. 778 | $machineMM = Get-MachineMM 779 | if(IsVirtualMachine($machineMM.Model)) 780 | { 781 | Write-Message "Warn" "Script is running at virtual machine, ignore native boot setting." 782 | Write-Message "Trace" "Machine manufacturer-model: $($machineMM.Manufacturer) - $($machineMM.Model)" 783 | return 784 | } 785 | 786 | #mount VHD as drive. The cmdlet "Mount-VHD" needs Hyper-V role, it cannot be used to anywhere. 787 | #So it is deprecated since it needs Hyper-V to be installed. 788 | $VHDVolume = (Mount-DiskImage -ImagePath $VHDPath -PassThru | Get-DiskImage | Get-Disk | Get-Partition| Get-Volume) 789 | 790 | $VHDMountDrive = $VHDVolume.DriveLetter + ":" 791 | Write-Message "Trace" ("VHD(X) was mounted to drive $($VHDMountDrive).") 792 | 793 | #run BCDBoot to add the VHD to system boot ment 794 | $CommandNativeBoot = "BCDBOOT $VHDMountDrive\Windows" 795 | Write-Message "Trace" ("Running command: $($CommandNativeBoot) ...") 796 | If ($TestMode) 797 | { 798 | Write-Message "Trace" "Running in Test Mode, BCDBoot will not run." 799 | } 800 | Else 801 | { 802 | Write-Message "Trace" "Run BCDboot command to add boot entry..." 803 | Invoke-Expression -Command $CommandNativeBoot 804 | } 805 | 806 | 807 | if ($Restart) 808 | { 809 | Write-Message "Trace" ("System restart option was enabled. Computer will be restarted in 30 seconds...") 810 | 811 | If ($TestMode) 812 | { 813 | Write-Message "Trace" "Running in Test Mode, system will not restart." 814 | 815 | Invoke-Expression -Command "BCDEdit" 816 | } 817 | Else 818 | { 819 | Write-Message "Trace" "Not test mode, restart computer in 30 seconds..." 820 | #Show count down progress bar 821 | for ($i = $RestartTimeOut; $i -ge 0; $i-- ) 822 | { 823 | $Counter = [decimal]::round(($i/$RestartTimeOut)*100) 824 | Write-Progress -activity "Restart counting down..." -status "$($i) second(s) to restart" -percentcomplete $Counter 825 | Start-Sleep 1 826 | } 827 | Restart-Computer -Force 828 | } 829 | } 830 | else 831 | { 832 | #Dismount the VHD file 833 | If (! $TestMode) 834 | { 835 | Write-Message "Info" "Please restart the computer manually!" 836 | } 837 | Invoke-Expression -Command "BCDEdit" 838 | } 839 | 840 | Write-Message "Trace" "Dismount VHD drive." 841 | Dismount-DiskImage -ImagePath $VHDPath 842 | 843 | } 844 | 845 | 846 | #Get Windows Image informtion 847 | <# return information like following: 848 | ImageIndex : 1 849 | ImageName : 850 | ImageDescription : 851 | ImageSize : 8,124,366,848 bytes 852 | Architecture : x64 853 | Hal : 854 | Version : 10.0.14300.1000 855 | SPBuild : 1000 856 | SPLevel : 0 857 | EditionId : Enterprise 858 | InstallationType : Client 859 | ProductType : WinNT 860 | ProductSuite : Terminal Server 861 | SystemRoot : Windows 862 | Languages : en-US (Default) 863 | #> 864 | function Get-WindowsImageInfo 865 | { 866 | [CmdletBinding()] 867 | param 868 | ( 869 | [Parameter(Mandatory = $True, ValueFromPipeline = $false)] 870 | [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 871 | [string]$ImagePath, 872 | 873 | [Parameter(Mandatory = $true, ValueFromPipeline = $false)] 874 | [UInt32]$ImageIndex 875 | ) 876 | 877 | #get the image file extension to identify the type: ISO or WIM 878 | $imageFile = Get-Item $ImagePath 879 | $imageType = $imageFile.Extension 880 | 881 | switch -Wildcard ($imageType.ToUpper()) { 882 | ".ISO" 883 | { 884 | #Mount ISO. Get-Volume can get the voume object directly from Mount-DiskImage (mount ISO file, not VHD file). 885 | $mountISOVolume = (Mount-DiskImage -ImagePath $ImagePath -PassThru | Get-Volume) 886 | $ISOMountDrive = $mountISOVolume.DriveLetter + ":" 887 | $imageWIMPath = Join-Path $ISOMountDrive "Sources\Install.WIM" 888 | } 889 | 890 | ".WIM" 891 | { 892 | #doesn't need to mount the image 893 | $imageWIMPath = $ImagePath 894 | } 895 | 896 | ".VHD*" 897 | { 898 | #doesn't need to mount the image 899 | $imageWIMPath = $ImagePath 900 | } 901 | <# 902 | ".VHDX" 903 | { 904 | #doesn't need to mount the image 905 | $imageWIMPath = $imagePath 906 | } 907 | 908 | #> 909 | Default 910 | { 911 | Write-Message "Err" "Unrecognized image type: $imageType" 912 | return $null 913 | } 914 | } 915 | 916 | try 917 | { 918 | $images = Get-WindowsImage -ImagePath $imageWIMPath -ErrorAction Stop 919 | $imageAllIndex = @() 920 | foreach ($imageItem in $images) 921 | { 922 | $imageAllIndex += $imageItem.ImageIndex 923 | } 924 | 925 | #if the ImageIndex is in the image, then select it. 926 | #if not, use the first one. 927 | if ($imageAllIndex -contains $ImageIndex) 928 | { 929 | $ImageIndex = $ImageIndex 930 | } 931 | else { 932 | $ImageIndex = $images[0].ImageIndex 933 | } 934 | 935 | $imageInfo = Get-WindowsImage -ImagePath $imageWIMPath -Index $imageIndex 936 | 937 | } 938 | catch { 939 | Write-Message "Err" "$($Error[0])" 940 | $imageInfo = $null 941 | } 942 | finally 943 | { 944 | #dismount the ISO if mounted. 945 | if($mountISOVolume) 946 | { 947 | Dismount-DiskImage -ImagePath $ImagePath 948 | } 949 | } 950 | 951 | return $imageInfo 952 | } 953 | 954 | 955 | #Get OS type from image 956 | function Get-OSType ($imagePath) 957 | { 958 | $productType = "UNKNOWN" #default return value 959 | 960 | #if the image doesn't exist, return default value. 961 | if (!(Test-Path $imagePath)) 962 | { 963 | Write-Message "Err" "Cannot find the image: $($imagePath)" 964 | return $productType 965 | } 966 | 967 | #get image information and get the producty tpye. 968 | $imageInfo = Get-WindowsImageInfo -ImagePath $imagePath -ImageIndex 0 969 | if ($imageInfo) 970 | { 971 | $productType = $imageInfo.ProductType 972 | } 973 | 974 | return $productType 975 | } 976 | 977 | #check if OS is client. 978 | function IsDesktopOS ($OSType) { 979 | return ($OSType.Toupper() -eq "WINNT") 980 | } 981 | 982 | #check if OS is server. 983 | function IsServerOS ($OSType) { 984 | 985 | return ($OSType.Toupper() -eq "SERVERNT") 986 | } 987 | 988 | #if the image is Windows image based on the OS type from function Get-OSType 989 | function IsWindowsImage ($OSType) { 990 | 991 | if ($OSType -eq $null) { 992 | return $False 993 | } 994 | if (($OSType.ToUpper() -ne "SERVERNT") -and ($OSType.ToUpper() -ne "WINNT")) 995 | { 996 | return $False 997 | } 998 | 999 | return $True 1000 | } 1001 | 1002 | 1003 | #stop script log 1004 | function Stop-ScriptLog ($isTranscripting) { 1005 | if ($isTranscripting) 1006 | { 1007 | Stop-Transcript | Out-Null 1008 | } 1009 | } 1010 | 1011 | ########################################################################################## 1012 | 1013 | 1014 | ########################################################################################## 1015 | # Main 1016 | ########################################################################################## 1017 | 1018 | 1019 | 1020 | #Declare constant 1021 | 1022 | Set-Variable -Name SCRIPT_VERSION -Value "2.0.100.Main.20170218.1015.0.Release" -Option Constant 1023 | Set-Variable -Name ScriptName -Value $MyInvocation.MyCommand.Name -Option Constant 1024 | 1025 | #unattend 1026 | Set-Variable -Name UnattendTemplate_AMD64_Server -Value $(Join-Path $PSScriptRoot "unattend_amd64_Server.xml") -Option Constant 1027 | Set-Variable -Name UnattendTemplate_AMD64_Client -Value $(Join-Path $PSScriptRoot "unattend_amd64_Client.xml") -Option Constant 1028 | 1029 | #default vault in unattend 1030 | Set-Variable -Name DefaultComputerName -Value "*" -Option Constant #random computer name 1031 | Set-Variable -Name DEFAULT_ADMIN_PASSWORD -Value "Local@123" 1032 | 1033 | #default time zone value 1034 | Set-Variable -Name DEFAULT_TIME_ZONE -Value "Pacific Standard Time" -Option Constant 1035 | Set-Variable -Name CHINA_TIME_ZONE -Value "China Standard Time" -Option Constant 1036 | 1037 | #restart timeout to 30 seconds when enable native boot and enable restart switch 1038 | Set-Variable -Name RestartTimeOut -Value 30 -Option Constant 1039 | 1040 | #Log 1041 | Set-Variable -Name LogFileFolderName -Value "LogFiles" -Option Constant 1042 | 1043 | 1044 | #Generate workspace name 1045 | try 1046 | { 1047 | $WorkSpaceName = New-Guid #Only works in PowerShell version 5 and above. 1048 | } 1049 | catch 1050 | { 1051 | $WorkSpaceName = [Guid]::NewGuid().ToString() 1052 | } 1053 | 1054 | #Enable transcripting as Log file 1055 | $isTranscripting = $False #transcripting has not been started at the begining. 1056 | 1057 | $LogFolder = Join-Path $PSScriptRoot $LogFileFolderName 1058 | $LogFilePathCurrent = Join-Path $LogFolder "$($ScriptName.Split('.')[-2]).$(Get-Date -UFormat "%Y%m%d%H%M%S").$($WorkSpaceName).Log" 1059 | 1060 | 1061 | if (Test-Path $LogFolder) { 1062 | #check the log file size, remove oldest one if currnt log size is larger than 3MB 1063 | } 1064 | else { 1065 | mkdir $LogFolder -Force | Out-Null 1066 | } 1067 | 1068 | # Start transcripting and set the indicator. If it's already running, we'll get an exception and swallow it. 1069 | Try 1070 | { 1071 | Start-Transcript -Path $LogFilePathCurrent -Force -ErrorAction SilentlyContinue | Out-Null 1072 | $isTranscripting = $true 1073 | } 1074 | Catch 1075 | { 1076 | Write-Message "Trace" "Transcripting is already running." 1077 | $isTranscripting = $false 1078 | } 1079 | 1080 | 1081 | # Banner text displayed during each run. 1082 | $banner = '='*80 1083 | $ScriptHeader = @" 1084 | $banner 1085 | VHD(X) Deployment for Windows(R) Server 2016. Developed by Wei Luo 1086 | 1087 | THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. 1088 | Version $SCRIPT_VERSION 1089 | $banner 1090 | 1091 | "@ 1092 | 1093 | Write-Message "TEXT" $ScriptHeader 1094 | 1095 | $StartTime= Get-Date -UFormat "%Y-%m-%d %H:%M:%S" #-Format yyyy-MM-dd/HH:MM:SS 1096 | 1097 | Write-Message "Info" "Script $ScriptName starts at $StartTime. Version: $($SCRIPT_VERSION)" 1098 | $LocalHostName = Get-LocalHostName 1099 | Write-Message "Info" "Script is running at computer $($LocalHostName)." 1100 | 1101 | # Check if PowerShell console is running at elevated mode. 1102 | If (!(Test-PSConsoleRunAsAdmin)) 1103 | { 1104 | Write-Message "Info" "Console is not running as Administrator, restrting it..." 1105 | Start-PSConsoleAsAdmin 1106 | Stop-ScriptLog $isTranscripting 1107 | exit 1108 | } 1109 | 1110 | Write-Message "Trace" "Workspace name: $($WorkSpaceName)" 1111 | 1112 | ########################################################################################## 1113 | #process parameters 1114 | # 1115 | 1116 | #Computer name for Unattend. Set it as local host name if no value from parameter. 1117 | If(! $ComputerName) 1118 | { 1119 | $ComputerName = $LocalHostName 1120 | } 1121 | 1122 | #Warning if enable Hyper-V feature or apply packages 1123 | if (($PSCmdlet.ParameterSetName -ne 'Info') -and ((! $DisableHyperV) -or ($Package))) 1124 | { 1125 | Write-Message "Warn" "The feature to install Hyper-V role or apply packages need latest servicing command.` 1126 | Make sure same or higher version servicing command is using than source image file." 1127 | } 1128 | 1129 | #Time zone for unattend 1130 | $TimeZone = $DEFAULT_TIME_ZONE 1131 | if ($ChinaTime) { 1132 | $TimeZone = $CHINA_TIME_ZONE 1133 | } 1134 | Write-Message "Trace" "VHD(X) Time zone: $($TimeZone)" 1135 | 1136 | #Get the partition style 1137 | $VHDPartitionStyle = 'GPT' 1138 | If ($MBRPartition) 1139 | { 1140 | $VHDPartitionStyle = 'MBR' 1141 | } 1142 | 1143 | # Check if VHD(X) file exists. 1144 | $bVHDExisted = Test-Path $VHDPath 1145 | Write-Message "Trace" "$($VHDPath) existence: $($bVHDExisted.toString())" 1146 | 1147 | #handle the parameter set 1148 | Switch -Wildcard($PSCmdlet.ParameterSetName) 1149 | { 1150 | #Create new VHD from media (ISO, WIM) to use or as template . 1151 | "NewVHD*" 1152 | { 1153 | If ($CreateVHDTemplate) 1154 | { 1155 | Write-Message "Info" "Creating virtual disk template..." 1156 | } 1157 | else { 1158 | Write-Message "Info" "Creating new $($VHDFormat) file..." 1159 | } 1160 | 1161 | #check the OS type and edition 1162 | Write-Message "Info" "Source file: $($SourcePath)" 1163 | $imageInfo = Get-WindowsImageInfo -ImagePath $SourcePath -ImageIndex 0 1164 | 1165 | $OSType = $imageInfo.ProductType #Get-OSType($SourcePath) 1166 | $bWindowsImage = IsWindowsImage($OSType) 1167 | if (! $bWindowsImage) 1168 | { 1169 | Write-Message "Err" "The source file is not valid Windows image file. Please check it." 1170 | Stop-ScriptLog $isTranscripting 1171 | Exit -1 1172 | } 1173 | 1174 | #if not Server OS, then force swith IsDesktop and disable Hyper-V 1175 | $IsDesktop = IsDesktopOS($OSType) 1176 | if ($IsDesktop) 1177 | { 1178 | Write-Message "Info" "Source file is Windows Desktop installation media." 1179 | $DisableHyperV = $True 1180 | 1181 | #check if the Edition is correct. 1182 | if (($Edition -ne "Enterprise") -and ($Edition -ne "Professional") -and ($Edition -ne "Ultimate")) 1183 | { 1184 | $Edition = $imageInfo.EditionId 1185 | } 1186 | } 1187 | else { 1188 | Write-Message "Info" "Source file is Windows Server installation media." 1189 | } 1190 | 1191 | #Check if the target path exists. If not, then create it. 1192 | if (! $bVHDExisted) { 1193 | mkdir $VHDPath -Force -ErrorAction SilentlyContinue | Out-Null 1194 | } 1195 | 1196 | # Generate VHD(X) file name if CreateVHDTemplate is enabled. 1197 | # Name convention: SourcePath.[Hyper-V].VHDSize.VHDPartitionStyle.VHDFormat 1198 | If ($CreateVHDTemplate) 1199 | { 1200 | If ($DisableHyperV) 1201 | { 1202 | $VHDPath = Join-Path $VHDPath "$([io.path]::GetFileNameWithoutExtension($SourcePath)).$($Edition.ToUpper()).$($VHDSize/1GB)GB.$($VHDPartitionStyle).$($VHDFormat)" 1203 | } 1204 | Else 1205 | { 1206 | $VHDPath = Join-Path $VHDPath "$([io.path]::GetFileNameWithoutExtension($SourcePath)).$($Edition.ToUpper()).Hyper-V.$($VHDSize/1GB)GB.$($VHDPartitionStyle).$($VHDFormat)" 1207 | } 1208 | } 1209 | 1210 | If(Test-Path $VHDPath) 1211 | { 1212 | Write-Message "Err" "VHD(X) file $($VHDPath) already exists. Plesae rename the existed template file and try again.Script quits." 1213 | Stop-ScriptLog $isTranscripting 1214 | Exit -1 1215 | } 1216 | Write-Message "Info" "Creating file $($VHDPath)..." 1217 | 1218 | #trace parameters 1219 | Write-Message "Trace" "Target VHD(X) file: $($VHDPath)" 1220 | Write-Message "Trace" "Source image path: $($SourcePath)" 1221 | Write-Message "Trace" "VHD(X) partition type: $($VHDPartitionStyle)" 1222 | Write-Message "Trace" "VHD(X) Size: $($VHDSize/1GB)GB" 1223 | Write-Message "Trace" "VHD(X) Format: $($VHDFormat)" 1224 | Write-Message "Trace" "OS edition: $($Edition)" 1225 | Write-Message "Trace" "Disable Hyper-V feature: $($DisableHyperV.ToString())" 1226 | 1227 | [System.Collections.ArrayList] $Features = @( 1228 | "Microsoft-Hyper-V" 1229 | "Microsoft-Hyper-V-Management-Clients" 1230 | "Microsoft-Hyper-V-Management-PowerShell" 1231 | ) 1232 | #To support Server core, cannot install management tools 1233 | if ($Edition.ToUpper().Contains("CORE")) 1234 | { 1235 | $Features.Remove("Microsoft-Hyper-V-Management-Clients") 1236 | } 1237 | 1238 | # splatting 1239 | $ConvertWindowsImageParam = @{ 1240 | SourcePath = $SourcePath 1241 | VHDPath = $VHDPath 1242 | RemoteDesktopEnable = $True 1243 | Passthru = $True 1244 | Edition = $Edition #"ServerDataCenter" 1245 | SizeBytes = $VHDSize 1246 | VHDPartitionStyle = $VHDPartitionStyle 1247 | VHDFormat = $VHDFormat 1248 | ExpandOnNativeBoot = $false 1249 | Feature = $Features 1250 | } 1251 | if ($DisableHyperV) 1252 | { 1253 | $ConvertWindowsImageParam.Remove("Feature") 1254 | } 1255 | 1256 | # Load (aka "dot-source"") the Function 1257 | $libraryFile = Join-Path $PSScriptRoot 'Convert-WindowsImage.ps1' 1258 | . $libraryFile 1259 | 1260 | # Create the VHD image 1261 | Write-Message "Info" "Run Convert-WindowsImage to create new VHD(X) and apply OS image..." 1262 | 1263 | Try 1264 | { 1265 | $vhd = Convert-WindowsImage @ConvertWindowsImageParam -ErrorAction Stop 1266 | } 1267 | Catch { 1268 | Write-Message "Err" "Failed to create new VHD(X) file $($VHDPath)." 1269 | Write-Message "Err" "$($Error[0])" 1270 | 1271 | Stop-ScriptLog $isTranscripting 1272 | Exit -1 1273 | } 1274 | 1275 | #Don't remove following code since the Dism error (in WS2012 R2) cannot be caught by Try/Catch 1276 | If (!$vhd) 1277 | { 1278 | Write-Message "Err" "Failed to create new VHD(X) file $($VHDPath)." 1279 | Stop-ScriptLog $isTranscripting 1280 | Exit -1 1281 | } 1282 | 1283 | If($CreateVHDTemplate) 1284 | { 1285 | Write-Message "Info" "VHD(X) template file $($VHDPath) was created." 1286 | 1287 | $ComputerName = $DefaultComputerName 1288 | Write-Message "Trace" "Set computer name to random in unattend.xml file: $($ComputerName)." 1289 | } 1290 | else { 1291 | Write-Message "Info" "VHD(X) file $($VHDPath) was created." 1292 | } 1293 | #then go to common tasks 1294 | } 1295 | 1296 | #Use VHD(X) template to copy a new VHD(X) 1297 | "CopyVHD" 1298 | { 1299 | If($bVHDExisted) 1300 | { 1301 | Write-Message "ERR" "VHD(X) file $($VHDPath) already exists. Please input a new name." 1302 | Stop-ScriptLog $isTranscripting 1303 | Exit -1 1304 | } 1305 | 1306 | Write-Message "Trace" "Source VHD(X): $($SourceVHD)" 1307 | Write-Message "Info" "Copy VHD(X) file to $($VHDPath)..." 1308 | 1309 | $SourceVHDSizeMB = (Get-Item $SourceVHD).Length / 1MB 1310 | Write-Message "Trace" "Source VHD(X) file size: $($SourceVHDSizeMB) MB" 1311 | 1312 | Copy-Item -Path $($SourceVHD) -Destination $($VHDPath) 1313 | 1314 | #then go to common tasks 1315 | } 1316 | 1317 | #Edit provided VHD(X) file 1318 | "EditVHD" 1319 | { 1320 | Write-Message "Trace" "Script will edit current VHD(X) file $($VHDPath)." 1321 | If(!$bVHDExisted) 1322 | { 1323 | Write-Message "ERR" "VHD(X) file $($VHDPath) doesn't exists." 1324 | Stop-ScriptLog $isTranscripting 1325 | Exit -1 1326 | } 1327 | #then go to common tasks 1328 | } 1329 | 1330 | 1331 | 1332 | "Info" 1333 | { 1334 | Write-Message 'Trace' "Get script information. Parameter count: $($PSBoundParameters.Count)" 1335 | if ($GetVersion) { 1336 | Write-Message "Info" "Script version: $($SCRIPT_VERSION)" 1337 | } 1338 | 1339 | if ($GetUsage -or $VerbosePreference) { 1340 | Invoke-Command {Help $PSCommandPath -Detailed} 1341 | } 1342 | 1343 | if($PSBoundParameters.Count -eq 0) 1344 | { 1345 | Invoke-Command {Help $PSCommandPath} 1346 | } 1347 | 1348 | Stop-ScriptLog $isTranscripting 1349 | exit 0 1350 | } 1351 | } 1352 | 1353 | #Commn tasks... 1354 | #0. check if VHD(X) includes Windows Destkop or Server. 1355 | #1. Process unattend 1356 | #2. Enable native boot 1357 | 1358 | 1359 | # check if VHD(X) file Windows edition and producty type. 1360 | # set IsDesktop if it is Windows client. 1361 | if (! $CreateVHDTemplate) 1362 | { 1363 | $imageInfo = Get-WindowsImageInfo -ImagePath $VHDPath -ImageIndex 0 1364 | $WindowsInfo = "Windows.$($imageInfo.InstallationType).$($imageInfo.EditionID).$($imageInfo.Version)" 1365 | Write-Message "Info" "Windows image information: $($WindowsInfo) `n`t $($imageInfo.ProductName)" 1366 | 1367 | #if not Server OS, then force swith IsDesktop and disable Hyper-V 1368 | $IsDesktop = IsDesktopOS($imageInfo.ProductType) 1369 | } 1370 | 1371 | 1372 | # Customize unattend file, install drivers and packages if provided from parameter 1373 | If(! $DisableUnattend) 1374 | { 1375 | 1376 | $UnattendTemplate = $UnattendTemplate_AMD64_Server 1377 | If($IsDesktop) 1378 | { 1379 | $UnattendTemplate = $UnattendTemplate_AMD64_Client 1380 | } 1381 | 1382 | #prepare the parameter for unattend processing 1383 | $UnattendPara = @{ 1384 | WorkSpaceName = $WorkSpaceName 1385 | VHDPath = $VHDPath 1386 | # UnattendFile = $UnattendFile 1387 | UnattendTemplate = $UnattendTemplate 1388 | # UnattendWindowsFolder = $UnattendWindowsFolder 1389 | EnableAutoLogon = $EnableAutoLogon 1390 | AutologonCount = $AutoLogonCount 1391 | LocalAdminAccount = $LocalAdminAccount 1392 | AdminPassword = $AdminPassword 1393 | ComputerName = $ComputerName 1394 | RegisteredOrganization = $RegisteredOrganization 1395 | RegisteredOwner = $RegisteredOwner 1396 | TimeZone = $TimeZone 1397 | Driver = $Driver 1398 | Package = $Package 1399 | } 1400 | 1401 | if (! $Driver) { 1402 | $UnattendPara.Remove("Driver") 1403 | } 1404 | 1405 | if (! $Package) { 1406 | $UnattendPara.Remove("Package") 1407 | } 1408 | 1409 | Write-Message "Info" ("Proceed the Unattend.xml file...") 1410 | ProcessUnattend @UnattendPara 1411 | } 1412 | 1413 | # Enable Native Boot 1414 | if ($EnableNativeBoot) 1415 | { 1416 | Write-Message "Info" "Native boot option was enabled. Proceed NativeBoot..." 1417 | 1418 | # Get VHD(X) file full path, native boot only use file full path. 1419 | $VHDPath = (get-item $VHDPath).FullName 1420 | $NativeBootPara = @{ 1421 | VHDPath = $VHDPath 1422 | Restart = $Restart 1423 | TestMode = $TestMode 1424 | } 1425 | EnableNativeBoot @NativeBootPara 1426 | } 1427 | 1428 | Write-Message 1429 | 1430 | $EndTime= Get-Date -UFormat "%Y-%m-%d %H:%M:%S" #-Format yyyy-MM-dd/HH:MM:SS 1431 | Write-Message "Info" "Script $ScriptName completed at $EndTime. Started at $StartTime." 1432 | 1433 | 1434 | # End the transcript 1435 | Stop-ScriptLog $isTranscripting 1436 | --------------------------------------------------------------------------------