├── _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
43 |
44 | %AUTOLOGON%
45 | Administrator
46 |
47 |
48 |
49 | %ADMINPASSWORD%
50 | false
51 |
52 |
53 |
54 | true
55 | true
56 |
57 | %TIMEZONE%
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/unattend_amd64_Client.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
43 |
44 | %AUTOLOGON%
45 | %LOGONCOUNT%
46 | %LOCALADMIN%
47 |
48 |
49 |
50 | %ADMINPASSWORD%
51 | false
52 |
53 |
54 |
55 |
56 | %ACCOUNTPASSWORD%
57 | false
58 |
59 | Additional Administrator Account
60 | %LOCALADMIN%
61 | Administrators
62 | %LOCALADMIN%
63 |
64 |
65 |
66 |
67 | true
68 | true
69 |
70 | %TIMEZONE%
71 |
72 |
73 |
74 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------