├── DDA
└── survey-dda.ps1
├── Daily-Status-Report
├── DailyStatusReport.ps1
└── readme.md
├── EvacuateDisk.ps1
├── Expand-VMConfig.ps1
├── GuestNameCheck.ps1
├── Image-Factory
├── .gitignore
├── Factory.ps1
├── FactoryVariables.ps1
├── images.csv
├── images.md
└── readme.md
├── LICENSE
├── Misc
└── BingCmdlets.ps1
├── README.md
├── Snippets
├── CopyTextFileIntoVM.ps1
├── PSCreds.ps1
├── Readme.md
├── WaitForPSDirect.ps1
├── WaitForVMDHCP.ps1
└── addNicWithIP.ps1
├── Ubuntu-VM-Build
├── BaseUbuntuBuild.ps1
└── readme.md
└── vmconnect.ps1
/DDA/survey-dda.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # These variables are device properties. For people who are very
3 | # curious about this, you can download the Windows Driver Kit headers and
4 | # look for pciprop.h. All of these are contained in that file.
5 | #
6 | $devpkey_PciDevice_DeviceType = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 1"
7 | $devpkey_PciDevice_RequiresReservedMemoryRegion = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 34"
8 | $devpkey_PciDevice_AcsCompatibleUpHierarchy = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 31"
9 |
10 | $devprop_PciDevice_DeviceType_PciConventional = 0
11 | $devprop_PciDevice_DeviceType_PciX = 1
12 | $devprop_PciDevice_DeviceType_PciExpressEndpoint = 2
13 | $devprop_PciDevice_DeviceType_PciExpressLegacyEndpoint = 3
14 | $devprop_PciDevice_DeviceType_PciExpressRootComplexIntegratedEndpoint= 4
15 | $devprop_PciDevice_DeviceType_PciExpressTreatedAsPci = 5
16 | $devprop_PciDevice_BridgeType_PciConventional = 6
17 | $devprop_PciDevice_BridgeType_PciX = 7
18 | $devprop_PciDevice_BridgeType_PciExpressRootPort = 8
19 | $devprop_PciDevice_BridgeType_PciExpressUpstreamSwitchPort = 9
20 | $devprop_PciDevice_BridgeType_PciExpressDownstreamSwitchPort = 10
21 | $devprop_PciDevice_BridgeType_PciExpressToPciXBridge = 11
22 | $devprop_PciDevice_BridgeType_PciXToExpressBridge = 12
23 | $devprop_PciDevice_BridgeType_PciExpressTreatedAsPci = 13
24 | $devprop_PciDevice_BridgeType_PciExpressEventCollector = 14
25 |
26 | $devprop_PciDevice_AcsCompatibleUpHierarchy_NotSupported = 0
27 | $devprop_PciDevice_AcsCompatibleUpHierarchy_SingleFunctionSupported = 1
28 | $devprop_PciDevice_AcsCompatibleUpHierarchy_NoP2PSupported = 2
29 | $devprop_PciDevice_AcsCompatibleUpHierarchy_Supported = 3
30 |
31 |
32 | write-host "Generating a list of PCI Express endpoint devices"
33 | $pnpdevs = Get-PnpDevice -PresentOnly
34 | $pcidevs = $pnpdevs | Where-Object {$_.InstanceId -like "PCI*"}
35 |
36 | foreach ($pcidev in $pcidevs) {
37 | Write-Host ""
38 | Write-Host ""
39 | Write-Host -ForegroundColor White -BackgroundColor Black $pcidev.FriendlyName
40 |
41 | $rmrr = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_RequiresReservedMemoryRegion).Data
42 | if ($rmrr -ne 0) {
43 | write-host -ForegroundColor Red -BackgroundColor Black "BIOS requires that this device remain attached to BIOS-owned memory. Not assignable."
44 | continue
45 | }
46 |
47 | $acsUp = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_AcsCompatibleUpHierarchy).Data
48 | if ($acsUp -eq $devprop_PciDevice_AcsCompatibleUpHierarchy_NotSupported) {
49 | write-host -ForegroundColor Red -BackgroundColor Black "Traffic from this device may be redirected to other devices in the system. Not assignable."
50 | continue
51 | }
52 |
53 | $devtype = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_DeviceType).Data
54 | if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressEndpoint) {
55 | Write-Host "Express Endpoint -- more secure."
56 | } else {
57 | if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressRootComplexIntegratedEndpoint) {
58 | Write-Host "Embedded Endpoint -- less secure."
59 | } else {
60 | if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressTreatedAsPci) {
61 | Write-Host -ForegroundColor Red -BackgroundColor Black "BIOS kept control of PCI Express for this device. Not assignable."
62 | } else {
63 | Write-Host -ForegroundColor Red -BackgroundColor Black "Old-style PCI device, switch port, etc. Not assignable."
64 | }
65 | continue
66 | }
67 | }
68 |
69 | $locationpath = ($pcidev | get-pnpdeviceproperty DEVPKEY_Device_LocationPaths).data[0]
70 |
71 | #
72 | # Now do a check for the interrupts that the device uses. Line-based interrupts
73 | # aren't assignable.
74 | #
75 | $doubleslashDevId = "*" + $pcidev.PNPDeviceID.Replace("\","\\") + "*"
76 | $irqAssignments = gwmi -query "select * from Win32_PnPAllocatedResource" | Where-Object {$_.__RELPATH -like "*Win32_IRQResource*"} | Where-Object {$_.Dependent -like $doubleslashDevId}
77 |
78 | #$irqAssignments | Format-Table -Property __RELPATH
79 |
80 | if ($irqAssignments.length -eq 0) {
81 | Write-Host -ForegroundColor Green -BackgroundColor Black " And it has no interrupts at all -- assignment can work."
82 | } else {
83 | #
84 | # Find the message-signaled interrupts. They are reported with a really big number in
85 | # decimal, one which always happens to start with "42949...".
86 | #
87 | $msiAssignments = $irqAssignments | Where-Object {$_.Antecedent -like "*IRQNumber=42949*"}
88 |
89 | #$msiAssignments | Format-Table -Property __RELPATH
90 |
91 | if ($msiAssignments.length -eq 0) {
92 | Write-Host -ForegroundColor Red -BackgroundColor Black "All of the interrupts are line-based, no assignment can work."
93 | continue
94 | } else {
95 | Write-Host -ForegroundColor Green -BackgroundColor Black " And its interrupts are message-based, assignment can work."
96 | }
97 | }
98 |
99 | #
100 | # Print out the location path, as that's the way to refer to this device that won't
101 | # change even if you add or remove devices from the machine or change the way that
102 | # the BIOS is configured.
103 | #
104 | $locationpath
105 | }
106 |
107 | #
108 | # Now look at the host as a whole. Asking whether the host supports SR-IOV
109 | # is mostly equivalent to asking whether it supports Discrete Device
110 | # Assignment.
111 | #
112 | if ((Get-VMHost).IovSupport -eq $false) {
113 | Write-Host ""
114 | write-host "Unfortunately, this machine doesn't support using them in a VM."
115 | Write-Host ""
116 | (Get-VMHost).IovSupportReasons
117 | }
--------------------------------------------------------------------------------
/Daily-Status-Report/DailyStatusReport.ps1:
--------------------------------------------------------------------------------
1 | # Variables
2 | $filedate = get-date
3 | $computer = gc env:computername
4 | $metricsData = get-vm | measure-vm
5 | $tableColor = "WhiteSmoke"
6 | $errorColor = "Red"
7 | $warningColor = "Yellow"
8 | $FromEmail = "email@email.org"
9 | $ToEmail = "email@email.org"
10 |
11 | # Establish Connection to SMTP server
12 | $smtpServer = "smtp.yourISP.com"
13 | $smtpCreds = new-object Net.NetworkCredential("yourUsername", "YourPassword")
14 | $smtp = new-object Net.Mail.SmtpClient($smtpServer)
15 | $smtp.UseDefaultCredentials = $false
16 | $smtp.Credentials = $smtpCreds
17 |
18 | # HTML Style Definition
19 | $message = ""
20 | $message = "
"
21 | $message = ""
30 |
31 | # Title
32 | $message = $message + "")}else{$_}}) `
120 | + " "
121 |
122 | # VM Metrics
123 | $message = $message + ""
124 | $message = $message + "Virtual Machine Utilization Report "
125 |
126 | $message = $message + "CPU utilization data: " + ($metricsData | `
127 | select-object @{Expression={$_.VMName};Label="Virtual Machine"}, `
128 | @{Expression={$_.AvgCPU};Label="Average CPU Utilization (MHz)"} `
129 | | ConvertTo-HTML -Fragment) `
130 | +" "
131 | $message = $message + "Memory utilization data: " + ($metricsData | `
132 | select-object @{Expression={$_.VMName};Label="Virtual Machine"}, `
133 | @{Expression={$_.AvgRAM};Label="Average Memory (MB)"}, `
134 | @{Expression={$_.MinRAM};Label="Minimum Memory (MB)"}, `
135 | @{Expression={$_.MaxRAM};Label="Maximum Memory (MB)"} `
136 | | ConvertTo-HTML -Fragment) `
137 | +" "
138 | $message = $message + "Network utilization data: " + ($metricsData | `
139 | select-object @{Expression={$_.VMName};Label="Virtual Machine"}, `
140 | @{Expression={"{0:N2}" -f (($_.NetworkMeteredTrafficReport | where-object {($_.Direction -eq "Inbound")}`
141 | | measure-object TotalTraffic -sum).sum / 1024)};Label="Inbound Network Traffic (GB)"}, `
142 | @{Expression={"{0:N2}" -f (($_.NetworkMeteredTrafficReport | where-object {($_.Direction -eq "Outbound")} `
143 | | measure-object TotalTraffic -sum).sum / 1024)};Label="Outbound Network Traffic (GB)"} `
144 | | ConvertTo-HTML -Fragment) `
145 | +" "
146 | $message = $message + "Disk utilization data: " + ($metricsData | `
147 | select-object @{Expression={$_.VMName};Label="Virtual Machine"}, `
148 | @{Expression={"{0:N2}" -f ($_.TotalDisk / 1024)};Label="Disk Space Used (GB)"} `
149 | | ConvertTo-HTML -Fragment) `
150 | +" "
151 | $message = $message + "Metering Duration data: " + ($metricsData | `
152 | select-object @{Expression={$_.VMName};Label="Virtual Machine"}, `
153 | @{Expression={$_.MeteringDuration};Label="Metering data duration"} `
154 | | ConvertTo-HTML -Fragment) `
155 | +" "
156 |
157 | # Reset metrics
158 | get-vm | Reset-VMResourceMetering
159 | get-vm | Enable-VMResourceMetering
160 |
161 | $message = $message + ""
162 |
163 | $email = new-object Net.Mail.MailMessage
164 | $email.Subject = "Hyper-V Server Report: $($filedate)"
165 | $email.From = new-object Net.Mail.MailAddress($FromEmail)
166 | $email.IsBodyHtml = $true
167 | $email.Body = $message
168 | $email.To.Add($ToEmail)
169 |
170 | # Send Email
171 | $smtp.Send($email)
172 |
--------------------------------------------------------------------------------
/Daily-Status-Report/readme.md:
--------------------------------------------------------------------------------
1 | For information read: http://blogs.msdn.com/b/virtual_pc_guy/archive/2014/03/17/my-daily-hyper-v-status-email-part-1-of-5.aspx
2 |
--------------------------------------------------------------------------------
/EvacuateDisk.ps1:
--------------------------------------------------------------------------------
1 | $sourcePath = "F:\"
2 | $destinationPath = "D:\"
3 |
4 | (Get-ChildItem $sourcePath -Recurse | Where-Object{$_.Attributes -ne "Directory"}).count
5 |
6 | Get-VM | ForEach-Object{
7 |
8 | $VM = $_
9 |
10 | # Check the configuration path
11 | $currentVMPath = $_.ConfigurationLocation
12 |
13 | if ($currentVMPath.StartsWith($sourcePath)) {
14 | $newVMPath = ($destinationPath) + ($currentVMPath.TrimStart($sourcePath))
15 | Move-VMStorage -VM $VM -VirtualMachinePath $newVMPath}
16 |
17 | # Check the snapshot path
18 | $currentSnapshotFilePath = $_.SnapshotFileLocation
19 |
20 | if ($currentSnapshotFilePath.StartsWith($sourcePath)) {
21 | $newSnapshotFilePath = ($destinationPath) + ($currentSnapshotFilePath.TrimStart($sourcePath))
22 | Move-VMStorage -VM $VM -SnapshotFilePath $newSnapshotFilePath}
23 |
24 | # Check the smart paging file path
25 | $currentSmartPagingFilePath = $_.SmartPagingFilePath
26 |
27 | if ($currentSmartPagingFilePath.StartsWith($sourcePath)) {
28 | $newSmartPagingFilePath = ($destinationPath) + ($currentSmartPagingFilePath.TrimStart($sourcePath))
29 | Move-VMStorage -VM $VM -SmartPagingFilePath $newSmartPagingFilePath}
30 |
31 | # Go over each hard drive
32 | $_.HardDrives | ForEach-Object{
33 | # Check the hard drive
34 | $currentHardDrivePath = $_.Path
35 |
36 | if ($currentHardDrivePath.StartsWith($sourcePath)) {
37 | $newHardDrivePath = ($destinationPath) + ($currentHardDrivePath.TrimStart($sourcePath))
38 | Move-VMStorage -VM $VM -VHDs @(@{"SourceFilePath" = $currentHardDrivePath; "DestinationFilePath" = $newHardDrivePath})}
39 | }
40 |
41 | }
42 |
43 | (Get-ChildItem $sourcePath -Recurse | Where-Object{$_.Attributes -ne "Directory"}).count
44 |
--------------------------------------------------------------------------------
/Expand-VMConfig.ps1:
--------------------------------------------------------------------------------
1 | Function Expand-VMConfig ($VMConfig) {
2 | $tempVM = (Compare-VM -Register -Path $VMConfig).VM
3 |
4 | write-host "VM Configuration Data"
5 | write-host "====================="
6 | $tempVM | Select-Object *
7 |
8 | write-host "VM Network Adapters"
9 | write-host "====================="
10 | $tempVM.NetworkAdapters
11 |
12 | write-host "VM Hard Drives"
13 | write-host "====================="
14 | $tempVM.HardDrives
15 |
16 | write-host "VM DVD Drives"
17 | write-host "====================="
18 | $tempVM.DVDDrives
19 |
20 | write-host "VM Floppy Drive"
21 | write-host "====================="
22 | $tempVM.FloppyDrive
23 |
24 | write-host "VM Fibre Channel"
25 | write-host "====================="
26 | $tempVM.FibreChannelHostBusAdapters
27 |
28 | write-host "VM COM1"
29 | write-host "====================="
30 | $tempVM.ComPort1
31 |
32 | write-host "VM COM2"
33 | write-host "====================="
34 | $tempVM.ComPort2}
35 |
--------------------------------------------------------------------------------
/GuestNameCheck.ps1:
--------------------------------------------------------------------------------
1 | # Get the virtual machine name from the parent partition
2 | $vmName = (Get-ItemProperty –path “HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters”).VirtualMachineName
3 | # Replace any non-alphanumeric characters with an underscore
4 | $vmName = [Regex]::Replace($vmName,"\W","_")
5 | # Trim names that are longer than 15 characters
6 | $vmName = $vmName.Substring(0,[System.Math]::Min(15, $vmName.Length))
7 |
8 | # Check the trimmed and cleaned VM name against the guest OS name
9 | # If it is different, change the guest OS name and reboot
10 | if ($env:computername -ne $vmName) {(Get-WmiObject win32_computersystem).Rename($vmName); shutdown -r -t 0}
11 |
--------------------------------------------------------------------------------
/Image-Factory/.gitignore:
--------------------------------------------------------------------------------
1 | images-with-product-keys.csv
2 | FactoryVariables-personal.ps1
--------------------------------------------------------------------------------
/Image-Factory/Factory.ps1:
--------------------------------------------------------------------------------
1 | param
2 | (
3 | [string]$variables=".\FactoryVariables.ps1",
4 | [string]$images
5 | )
6 |
7 | # Load variables from a seperate file - this when you pull down the latest factory file you can keep your paths / product keys etc...
8 | . $variables
9 | $startTime = get-date
10 |
11 |
12 | [string] $driveLetter = ""
13 |
14 |
15 | # Helper function to make sure that needed folders are present
16 | function checkPath
17 | {
18 | param
19 | (
20 | [string] $path
21 | )
22 | if (!(Test-Path $path))
23 | {
24 | $null = md $path;
25 | }
26 | }
27 |
28 | # Test that necessary paths and files exist
29 | # Don't create the workingDir automatically - if it doesn't exist, the variables probably need the be configured first.
30 | if(-not (Test-Path -Path $workingDir -PathType Container)) {
31 | Throw "Working directory $workingDir does not exist - edit FactoryVariables.ps1 to configure script"
32 | }
33 |
34 | # Create folders in workingDir if they don't exist
35 | checkPath "$($workingdir)\Share"
36 | checkPath "$($workingdir)\Bases"
37 | checkPath "$($workingdir)\ISOs"
38 | checkPath "$($workingdir)\Resources"
39 | checkPath "$($workingdir)\Resources\bits"
40 |
41 | ### Load Convert-WindowsImage - making sure it exists and is unblocked
42 | if(Test-Path -Path "$($workingDir)\resources\Convert-WindowsImage.ps1") {
43 | . "$($workingDir)\resources\Convert-WindowsImage.ps1"
44 | if(!(Get-Command Convert-WindowsImage -ErrorAction SilentlyContinue)) {
45 | Write-Host -ForegroundColor Green 'Convert-WindowsImage.ps1 could not be loaded. Unblock the script or check execution policy'
46 | Throw 'Convert-WindowsImage was not loaded'
47 | }
48 | } else {
49 | Write-Host -ForegroundColor Green "Please download Convert-WindowsImage.ps1 and place in $($workingDir)\Resources\"
50 | Write-Host -ForegroundColor Green "`nhttps://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f`n"
51 | Throw 'Missing Convert-WindowsImage.ps1 script'
52 | }
53 |
54 | # Check that PSWindowsUpdate module exists
55 | if(!(Test-Path -Path "$($workingDir)\Resources\bits\PSWindowsUpdate\PSWindowsUpdate.psm1")) {
56 | Write-Host -ForegroundColor Green "Please download PSWindowsUpdate and extract to $($workingDir)\Resources\bits"
57 | Write-Host -ForegroundColor Green "`nhttps://gallery.technet.microsoft.com/scriptcenter/2d191bcd-3308-4edd-9de2-88dff796b0bc`n"
58 | Throw 'Missing PSWindowsUpdate module'
59 | }
60 |
61 |
62 | ### Sysprep unattend XML
63 | $unattendSource = [xml]@"
64 |
65 |
66 |
67 |
68 |
69 | *
70 | Key
71 | Organization
72 | Owner
73 | TZ
74 |
75 |
76 | false
77 |
78 |
79 | 0
80 |
81 |
82 |
83 |
84 | true
85 | all
86 | @FirewallAPI.dll,-28752
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | true
95 | true
96 | true
97 | Work
98 | 1
99 |
100 |
101 |
102 | password
103 | True
104 |
105 |
106 |
107 |
108 | password
109 | True
110 |
111 | Demo
112 | Administrators
113 | demo
114 |
115 |
116 |
117 |
118 |
119 | password
120 |
121 | true
122 | 1000
123 | Administrator
124 |
125 |
126 |
127 | %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -File %SystemDrive%\Bits\Logon.ps1
128 | 1
129 |
130 |
131 |
132 |
133 | en-us
134 | en-us
135 | en-us
136 | en-us
137 | en-us
138 |
139 |
140 |
141 | "@
142 |
143 | function CSVLogger {
144 | param
145 | (
146 | [string] $vhd,
147 | [switch] $sysprepped
148 | );
149 |
150 | $createLogFile = $false;
151 | $entryExists = $false;
152 | $logCsv = @();
153 | $newEntry = $null;
154 |
155 | # Check if the log file exists
156 | if (-not (Test-Path $logFile))
157 | {
158 | $createLogFile = $true;
159 | }
160 | else
161 | {
162 | $logCsv = import-csv $logFile;
163 |
164 | if (($logCsv.Image -eq $null) -or `
165 | ($logCsv.Created -eq $null) -or `
166 | ($logCsv.Sysprepped -eq $null) -or `
167 | ($logCsv.Checked -eq $null))
168 | {
169 | # Something is wrong with the log file
170 | cleanupFile $logFile;
171 | $createLogFile = $true;
172 | }
173 | }
174 |
175 | if ($createLogFile)
176 | {
177 | $logCsv = @();
178 | }
179 | else
180 | {
181 | $logCsv = Import-Csv $logFile;
182 | }
183 |
184 | # If we find an entry for the VHD, update it
185 | foreach ($entry in $logCsv)
186 | {
187 | if ($entry.Image -eq $vhd)
188 | {
189 | $entryExists = $true;
190 | $entry.Checked = ((Get-Date).ToShortDateString() + "::" + (Get-Date).ToShortTimeString());
191 |
192 | if ($sysprepped)
193 | {
194 | $entry.Sysprepped = ((Get-Date).ToShortDateString() + "::" + (Get-Date).ToShortTimeString());
195 | }
196 | }
197 | }
198 |
199 | # if no entry is found, create a new one
200 | if (-not $entryExists)
201 | {
202 | $newEntry = New-Object PSObject -Property @{
203 | Image = $vhd
204 | Created = ((Get-Date).ToShortDateString() + "::" + (Get-Date).ToShortTimeString())
205 | Sysprepped = ((Get-Date).ToShortDateString() + "::" + (Get-Date).ToShortTimeString())
206 | Checked = ((Get-Date).ToShortDateString() + "::" + (Get-Date).ToShortTimeString())
207 | };
208 | }
209 |
210 | # Write out the CSV file
211 | $logCsv | Export-CSV $logFile -NoTypeInformation;
212 | if (-not ($newEntry -eq $null))
213 | {
214 | $newEntry | Export-CSV $logFile -NoTypeInformation -Append;
215 | }
216 | }
217 |
218 | function Logger {
219 | param
220 | (
221 | [string]$systemName,
222 | [string]$message
223 | );
224 |
225 | # Function for displaying formatted log messages. Also displays time in minutes since the script was started
226 | write-host (Get-Date).ToShortTimeString() -ForegroundColor Cyan -NoNewline;
227 | write-host " - [" -ForegroundColor White -NoNewline;
228 | write-host $systemName -ForegroundColor Yellow -NoNewline;
229 | write-Host "]::$($message)" -ForegroundColor White;
230 | }
231 |
232 | # Helper function for no error file cleanup
233 | function cleanupFile
234 | {
235 | param
236 | (
237 | [string] $file
238 | )
239 |
240 | if (Test-Path $file)
241 | {
242 | Remove-Item $file -Recurse > $null;
243 | }
244 | }
245 |
246 |
247 |
248 | function GetUnattendChunk
249 | {
250 | param
251 | (
252 | [string] $pass,
253 | [string] $component,
254 | [xml] $unattend
255 | );
256 |
257 | # Helper function that returns one component chunk from the Unattend XML data structure
258 | return $Unattend.unattend.settings | ? pass -eq $pass `
259 | | select -ExpandProperty component `
260 | | ? name -eq $component;
261 | }
262 |
263 | function makeUnattendFile
264 | {
265 | param
266 | (
267 | [string] $key,
268 | [string] $logonCount,
269 | [string] $filePath,
270 | [bool] $desktop = $false,
271 | [bool] $is32bit = $false
272 | );
273 |
274 | # Composes unattend file and writes it to the specified filepath
275 |
276 | # Reload template - clone is necessary as PowerShell thinks this is a "complex" object
277 | $unattend = $unattendSource.Clone();
278 |
279 | # Customize unattend XML
280 | GetUnattendChunk "specialize" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.ProductKey = $key};
281 | GetUnattendChunk "specialize" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.RegisteredOrganization = $Organization};
282 | GetUnattendChunk "specialize" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.RegisteredOwner = $Owner};
283 | GetUnattendChunk "specialize" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.TimeZone = $Timezone};
284 | GetUnattendChunk "oobeSystem" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.UserAccounts.AdministratorPassword.Value = $adminPassword};
285 | GetUnattendChunk "oobeSystem" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.AutoLogon.Password.Value = $adminPassword};
286 | GetUnattendChunk "oobeSystem" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.AutoLogon.LogonCount = $logonCount};
287 |
288 | if ($desktop)
289 | {
290 | GetUnattendChunk "oobeSystem" "Microsoft-Windows-Shell-Setup" $unattend | %{$_.UserAccounts.LocalAccounts.LocalAccount.Password.Value = $userPassword};
291 | # HideLocalAccountScreen setting applies only to the Windows Server editions, and only to Windows Server 2012 and above
292 | # This will remove the setting for desktop images
293 | $ns = New-Object System.Xml.XmlNamespaceManager($unattend.NameTable);
294 | $ns.AddNamespace("ns", $unattend.DocumentElement.NamespaceURI);
295 | $node = $unattend.SelectSingleNode("//ns:HideLocalAccountScreen", $ns);
296 | $node.ParentNode.RemoveChild($node) | Out-Null;
297 | }
298 | else
299 | {
300 | # Desktop needs a user other than "Administrator" to be present
301 | # This will remove the creation of the other user for server images
302 | $ns = New-Object System.Xml.XmlNamespaceManager($unattend.NameTable);
303 | $ns.AddNamespace("ns", $unattend.DocumentElement.NamespaceURI);
304 | $node = $unattend.SelectSingleNode("//ns:LocalAccounts", $ns);
305 | $node.ParentNode.RemoveChild($node) | Out-Null;
306 |
307 | if ($FriendlyName.substring(0,19) -eq "Windows Server 2008")
308 | {
309 | # HideLocalAccountScreen setting applies only to the Windows Server editions, and only to Windows Server 2012 and above
310 | # This will remove the setting for Windows Server 2008 R2 images
311 | $ns = New-Object System.Xml.XmlNamespaceManager($unattend.NameTable);
312 | $ns.AddNamespace("ns", $unattend.DocumentElement.NamespaceURI);
313 | $node = $unattend.SelectSingleNode("//ns:HideLocalAccountScreen", $ns);
314 | $node.ParentNode.RemoveChild($node) | Out-Null;
315 | }
316 | }
317 |
318 | if ($is32bit)
319 | {
320 | $unattend.InnerXml = $unattend.InnerXml.Replace('processorArchitecture="amd64"', 'processorArchitecture="x86"');
321 | }
322 |
323 | # Write it out to disk
324 | cleanupFile $filePath; $Unattend.Save($filePath);
325 | }
326 |
327 | function createRunAndWaitVM
328 | {
329 | param
330 | (
331 | [string] $vhd,
332 | [string] $gen
333 | );
334 |
335 | # Function for whenever I have a VHD that is ready to run
336 | New-VM $factoryVMName -MemoryStartupBytes $VMMemory -VHDPath $vhd -Generation $Gen -SwitchName $virtualSwitchName -ErrorAction Stop| Out-Null
337 |
338 | If($UseVLAN) {
339 | Get-VMNetworkAdapter -VMName $factoryVMName | Set-VMNetworkAdapterVlan -Access -VlanId $VlanId
340 | }
341 |
342 | set-vm -Name $factoryVMName -ProcessorCount 2;
343 | Start-VM $factoryVMName;
344 |
345 | # Give the VM a moment to start before we start checking for it to stop
346 | Sleep -Seconds 10;
347 |
348 | # Wait for the VM to be stopped for a good solid 5 seconds
349 | do
350 | {
351 | $state1 = (Get-VM | ? name -eq $factoryVMName).State;
352 | Start-Sleep -Seconds 5;
353 |
354 | $state2 = (Get-VM | ? name -eq $factoryVMName).State;
355 | Start-Sleep -Seconds 5;
356 | }
357 | until (($state1 -eq "Off") -and ($state2 -eq "Off"))
358 |
359 | # Clean up the VM
360 | Remove-VM $factoryVMName -Force;
361 | }
362 |
363 | function MountVHDandRunBlock
364 | {
365 | param
366 | (
367 | [string]$vhd,
368 | [scriptblock]$block,
369 | [switch]$ReadOnly
370 | );
371 |
372 | # This function mounts a VHD, runs a script block and unmounts the VHD.
373 | # Drive letter of the mounted VHD is stored in $driveLetter - can be used by script blocks
374 | if($ReadOnly) {
375 | $driveLetter = (Mount-VHD $vhd -ReadOnly -Passthru | Get-Disk | Get-Partition | Where { -not $_.IsHidden } | Get-Volume | Where { $_.DriveLetter }).DriveLetter;
376 | } else {
377 | $driveLetter = (Mount-VHD $vhd -Passthru | Get-Disk | Get-Partition | Where { -not $_.IsHidden } | Get-Volume | Where { $_.DriveLetter } ).DriveLetter;
378 | }
379 | & $block;
380 |
381 | Dismount-VHD $vhd;
382 |
383 | # Wait 2 seconds for activity to clean up
384 | Start-Sleep -Seconds 2;
385 | }
386 |
387 | ### Update script block
388 | $updateCheckScriptBlock = {
389 | function Logger {
390 | param
391 | (
392 | [string]$message
393 | );
394 |
395 | # Function for displaying formatted log messages. Also displays time in minutes since the script was started
396 | write-host (Get-Date).ToShortTimeString() -ForegroundColor Cyan -NoNewline;
397 | write-host " - ::$($message)" -ForegroundColor White;
398 | }
399 |
400 | # Clean up unattend file if it is there
401 | if (Test-Path "$ENV:SystemDrive\Unattend.xml")
402 | {
403 | Remove-Item -Force "$ENV:SystemDrive\Unattend.xml"
404 | }
405 |
406 | # Elements of the script rely on PowerShell 3.0, so install updated PowerShell if necessary
407 | if ($PSVersionTable.PSVersion.Major -lt 3)
408 | {
409 | logger "Not running PowerShell 3.0 or above"
410 | # First check .NET Framework 4.5 full version prerequisite
411 | $DNVersion = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -name Version -EA 0 | Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} | Sort-Object version -Descending | Select-Object -ExpandProperty Version -First 1
412 | $DNVersions = $DNVersion.Split(".")
413 | $DNVersionMajor = $DNVersions[0]
414 | $DNVersionMinor = $DNVersions[1]
415 | $DNVersionBuild = $DNVersions[2]
416 |
417 | if (($DNVersionMajor -eq 4 -and $DNVersionMinor -ge 5) -or ($DNVersionMajor -ge 4))
418 | {
419 | logger ".NET prerequisites met"
420 | }
421 | else
422 | {
423 | logger ".NET update required"
424 | if (!(test-path -Path "C:\Temp"))
425 | {
426 | New-Item -ItemType Directory -Force -Path "C:\Temp" > $null
427 | }
428 | logger "Downloading .NET 4.5.2"
429 | $download = New-Object Net.WebClient
430 | $url = "http://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe"
431 | $file = ("C:\Temp\NDP452-KB2901907-x86-x64-AllOS-ENU.exe")
432 | $download.Downloadfile($url,$file)
433 | if (!(Test-Path -Path "C:\Temp\NDP452-KB2901907-x86-x64-AllOS-ENU.exe"))
434 | {
435 | logger "Download failed. Please check your Internet connection"
436 | }
437 | else
438 | {
439 | logger "Installing .NET 4.5.2"
440 | $InstallDotNet = Start-Process $file -ArgumentList "/q /norestart" -Wait -PassThru
441 | logger ".NET 4.5.2 installation complete"
442 | }
443 | }
444 |
445 | logger "Downloading Windows Management Framework 4.0"
446 | if (!(test-path -Path "C:\Temp"))
447 | {
448 | New-Item -ItemType Directory -Force -Path "C:\Temp" > $null
449 | }
450 | $download = New-Object Net.WebClient
451 | $url = "http://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-x64-MultiPkg.msu"
452 | $file = ("C:\Temp\Windows6.1-KB2819745-x64-MultiPkg.msu")
453 | $commandline = "C:\Temp\Windows6.1-KB2819745-x64-MultiPkg.msu /quiet /norestart"
454 | $download.Downloadfile($url,$file)
455 | if (!(Test-Path -Path "C:\Temp\Windows6.1-KB2819745-x64-MultiPkg.msu"))
456 | {
457 | logger "Download failed. Please check your Internet connection"
458 | }
459 | else
460 | {
461 | logger "Installing Windows Management Framework 4.0"
462 | $InstallWMF = Start-Process -FilePath 'wusa.exe' -ArgumentList "$commandline" -Wait -PassThru
463 | logger "Windows Management Framework 4.0 installation complete"
464 | }
465 | Invoke-Expression 'shutdown -r -t 0'
466 | }
467 |
468 | # Check to see if files need to be unblocked - if they do, do it and reboot
469 | if ((Get-ChildItem $env:SystemDrive\Bits | `
470 | Get-Item -Stream "Zone.Identifier" -ErrorAction SilentlyContinue).Count -gt 0)
471 | {
472 | Get-ChildItem $env:SystemDrive\Bits | Unblock-File;
473 | Invoke-Expression 'shutdown -r -t 0'
474 | }
475 |
476 | # To get here - the files are unblocked
477 | Import-Module $env:SystemDrive\Bits\PSWindowsUpdate\PSWindowsUpdate;
478 |
479 | # Set static IP address - do not change values here, change them in FactoryVariables.ps1
480 | $UseStaticIP = STATICIPBOOLPLACEHOLDER
481 | if($UseStaticIP) {
482 | $IP = 'IPADDRESSPLACEHOLDER'
483 | $MaskBits = 'SUBNETMASKPLACEHOLDER'
484 | $Gateway = 'GATEWAYPLACEHOLDER'
485 | $DNS = 'DNSPLACEHOLDER'
486 | $IPType = 'IPTYPEPLACEHOLDER'
487 |
488 | $adapter = Get-NetAdapter | Where-Object {$_.Status -eq 'up'}
489 | # Remove any existing IP, gateway from our ipv4 adapter
490 | If (($adapter | Get-NetIPConfiguration).IPv4Address.IPAddress) {
491 | $adapter | Remove-NetIPAddress -AddressFamily $IPType -Confirm:$false
492 | }
493 | If (($adapter | Get-NetIPConfiguration).Ipv4DefaultGateway) {
494 | $adapter | Remove-NetRoute -AddressFamily $IPType -Confirm:$false
495 | }
496 | # Configure the IP address and default gateway
497 | $adapter | New-NetIPAddress -AddressFamily $IPType `
498 | -IPAddress $IP `
499 | -PrefixLength $MaskBits `
500 | -DefaultGateway $Gateway
501 | # Configure the DNS client server IP addresses
502 | $adapter | Set-DnsClientServerAddress -ServerAddresses $DNS
503 | }
504 |
505 |
506 |
507 | # Need to add check for Internet connectivity due to Windows 7 driver load timing fail
508 | logger "Checking for Internet connection"
509 | do
510 | {
511 | Start-Sleep -Seconds 5;
512 | logger "Checking for Internet connection"
513 | } until (Test-Connection -computername www.microsoft.com)
514 |
515 | # Run pre-update script if it exists
516 | if (Test-Path "$env:SystemDrive\Bits\PreUpdateScript.ps1") {
517 | & "$env:SystemDrive\Bits\PreUpdateScript.ps1"
518 | }
519 |
520 | # Check if any updates are needed - leave a marker if there are
521 | logger "Checking for updates"
522 | if ((Get-WUList).Count -gt 0)
523 | {
524 | if (-not (Test-Path $env:SystemDrive\Bits\changesMade.txt))
525 | {
526 | New-Item $env:SystemDrive\Bits\changesMade.txt -type file > $null;
527 | }
528 | }
529 |
530 | # Apply all the updates
531 | logger "Applying the updates"
532 | Get-WUInstall -AcceptAll -IgnoreReboot -IgnoreUserInput -NotCategory "Language packs";
533 |
534 | # Run post-update script if it exists
535 | if (Test-Path "$env:SystemDrive\Bits\PostUpdateScript.ps1") {
536 | & "$env:SystemDrive\Bits\PostUpdateScript.ps1"
537 | }
538 |
539 | # Remove static IP address
540 | if($UseStaticIP) {
541 | $adapter = Get-NetAdapter | ? {$_.Status -eq "up"}
542 | $interface = $adapter | Get-NetIPInterface -AddressFamily $IPType
543 |
544 | If ($interface.Dhcp -eq "Disabled") {
545 | If (($interface | Get-NetIPConfiguration).Ipv4DefaultGateway) {
546 | $interface | Remove-NetRoute -Confirm:$false
547 | }
548 | $interface | Set-NetIPInterface -DHCP Enabled
549 | $interface | Set-DnsClientServerAddress -ResetServerAddresses
550 | }
551 | }
552 |
553 | # Reboot if needed - otherwise shutdown because we are done
554 | if (Get-WURebootStatus -Silent)
555 | {
556 | Invoke-Expression 'shutdown -r -t 0';
557 | }
558 | else
559 | {
560 | invoke-expression 'shutdown -s -t 0';
561 | }
562 | };
563 |
564 | function Set-UpdateCheckPlaceHolders {
565 | $block = $updateCheckScriptBlock | Out-String -Width 4096
566 |
567 | if($UseStaticIP) {
568 | $block = $block.Replace('$UseStaticIP = STATICIPBOOLPLACEHOLDER', '$UseStaticIP = $true')
569 | $block = $block.Replace('IPADDRESSPLACEHOLDER', $IP)
570 | $block = $block.Replace('SUBNETMASKPLACEHOLDER', $MaskBits)
571 | $block = $block.Replace('GATEWAYPLACEHOLDER', $Gateway)
572 | $block = $block.Replace('DNSPLACEHOLDER', $DNS)
573 | $block = $block.Replace('IPTYPEPLACEHOLDER', $IPType)
574 | } else {
575 | $block = $block.Replace('$UseStaticIP = STATICIPBOOLPLACEHOLDER', '$UseStaticIP = $false')
576 | }
577 | return $block
578 | }
579 |
580 | ### Sysprep script block
581 | $sysprepScriptBlock = {
582 | # Run pre-sysprep script if it exists
583 | if (Test-Path "$env:SystemDrive\Bits\PreSysprepScript.ps1") {
584 | & "$env:SystemDrive\Bits\PreSysprepScript.ps1"
585 | }
586 |
587 | # Remove Unattend entries from the autorun key if they exist
588 | foreach ($regvalue in (Get-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run).Property)
589 | {
590 | if ($regvalue -like "Unattend*")
591 | {
592 | # could be multiple unattend* entries
593 | foreach ($unattendvalue in $regvalue)
594 | {
595 | Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -name $unattendvalue
596 | }
597 | }
598 | }
599 |
600 | $unattendedXmlPath = "$ENV:SystemDrive\Bits\Unattend.xml";
601 | & "$ENV:SystemRoot\System32\Sysprep\Sysprep.exe" `/generalize `/oobe `/shutdown `/unattend:"$unattendedXmlPath";
602 | };
603 |
604 | ### Post Sysprep script block
605 | $postSysprepScriptBlock = {
606 | # Remove Unattend entries from the autorun key if they exist
607 | foreach ($regvalue in (Get-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run).Property)
608 | {
609 | if ($regvalue -like "Unattend*")
610 | {
611 | # could be multiple unattend* entries
612 | foreach ($unattendvalue in $regvalue)
613 | {
614 | Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -name $unattendvalue
615 | }
616 | }
617 | }
618 |
619 | # Run post-sysprep script if it exists
620 | if (Test-Path "$env:SystemDrive\Bits\PostSysprepScript.ps1") {
621 | & "$env:SystemDrive\Bits\PostSysprepScript.ps1"
622 | }
623 |
624 | # Clean up unattend file if it is there
625 | if (Test-Path "$ENV:SystemDrive\Unattend.xml")
626 | {
627 | Remove-Item -Force "$ENV:SystemDrive\Unattend.xml";
628 | }
629 |
630 | # Clean up bits
631 | if(Test-Path "$ENV:SystemDrive\Bits")
632 | {
633 | Remove-Item -Force -Recurse "$ENV:SystemDrive\Bits";
634 | }
635 |
636 | # Clean up temp
637 | if(Test-Path "$ENV:SystemDrive\Temp")
638 | {
639 | Remove-Item -Force -Recurse "$ENV:SystemDrive\Temp";
640 | }
641 |
642 | # Remove Demo user
643 | $computer = $env:computername
644 | $user = "Demo"
645 | if ([ADSI]::Exists("WinNT://$computer/$user"))
646 | {
647 | [ADSI]$server = "WinNT://$computer"
648 | $server.delete("user",$user)
649 | }
650 |
651 | # Put any code you want to run Post sysprep here
652 | Invoke-Expression 'shutdown -r -t 0';
653 | };
654 |
655 | # This is the main function of this script
656 | function Start-ImageFactory
657 | {
658 | <#
659 | .SYNOPSIS
660 | Creates or updates a windows image with the latest windows updates.
661 |
662 | .DESCRIPTION
663 | This function creates fully updated VHD images for deployment as virtual machines.
664 |
665 | The first time it is called for a particular windows type, it creates a fresh install in a Hyper-V virtual machine,
666 | updates it, and saves a sysprepped image. The unsysprepped VHD is kept so future runs only need to install new updates.
667 |
668 | Much of the configuration is done in FactoryVariables.ps1, which must be edited to suit your environment.
669 |
670 | .PARAMETER FriendlyName
671 | Used as the file name for the image.
672 |
673 | .PARAMETER ISOFile
674 | The ISO or WIM file to use as the base of the windows image.
675 |
676 | .PARAMETER SKUEdition
677 | The name of index number of the edition to use from the ISO/WIM file.
678 | eg. "Professional" or "ServerDataCenter"
679 |
680 | .PARAMETER ProductKey
681 | The product key to use for the unattended installation.
682 |
683 | .PARAMETER Desktop
684 | Set to $true for desktop windows versions. Creates a regular user account, which is required by the desktop unattended installation.
685 |
686 | .PARAMETER Is32Bit
687 | Set to $true for 32 bit images to create the unattend file correctly.
688 |
689 | .PARAMETER Generation2
690 | Create a generation 2 virtual machine. Default is generation 1
691 |
692 | .PARAMETER GenericSysprep
693 | Run the final sysprep without specifying a /unattend file. This is required by some deployment tools like SCVMM which will run their own sysprep when deploying.
694 |
695 | .NOTES
696 | This script requires two additional downloads:
697 | PSWindowsUpdate from https://gallery.technet.microsoft.com/scriptcenter/2d191bcd-3308-4edd-9de2-88dff796b0bc
698 |
699 | Convert-WindowsImage.ps1 from https://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f
700 |
701 | .LINK
702 | https://github.com/BenjaminArmstrong/Hyper-V-PowerShell
703 |
704 | .EXAMPLE
705 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter with GUI" -ISOFile c:\path\to\isos\server2012r2.iso -ProductKey "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9" -SKUEdition "ServerDataCenter"
706 |
707 | .EXAMPLE
708 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter Core - Gen 2" -ISOFile c:\path\to\isos\server2012r2.iso -ProductKey "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9" -SKUEdition "ServerDataCenterCore" -Generation2
709 |
710 | .EXAMPLE
711 | Start-ImageFactory -FriendlyName "Windows 8.1 Professional - 32 bit" -ISOFile c:\path\to\isos\windows81.iso -ProductKey "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9" -SKUEdition "Professional" -desktop $true -is32bit $true
712 |
713 | #>
714 |
715 |
716 | param
717 | (
718 | [Parameter(Mandatory=$true)][string]$FriendlyName,
719 | [string]$ISOFile,
720 | [string]$ProductKey,
721 | [string]$SKUEdition,
722 | [bool]$desktop = $false,
723 | [bool]$is32bit = $false,
724 | [switch]$Generation2,
725 | [bool] $GenericSysprep = $false
726 | );
727 |
728 | logger $FriendlyName "Starting a new cycle!"
729 |
730 | # Setup a bunch of variables
731 | $sysprepNeeded = $true;
732 |
733 | $VHDFormat = "vhdx";
734 | if ($LegacyVHD)
735 | {
736 | $VHDFormat = "vhd";
737 | }
738 |
739 | $baseVHD = "$($workingDir)\bases\$($FriendlyName)-base.$($VHDFormat)";
740 | $updateVHD = "$($workingDir)\$($FriendlyName)-update.$($VHDFormat)";
741 | $sysprepVHD = "$($workingDir)\$($FriendlyName)-sysprep.$($VHDFormat)";
742 | $finalVHD = "$($workingDir)\share\$($FriendlyName).$($VHDFormat)";
743 |
744 | $VHDPartitionStyle = "MBR";
745 | $Gen = 1;
746 | if ($Generation2)
747 | {
748 | $VHDPartitionStyle = "GPT";
749 | $Gen = 2;
750 | }
751 |
752 | # Verify product key
753 | if(-not ($ProductKey -imatch '^[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}$')) {
754 | logger $FriendlyName 'Invalid product key, skipping this product'
755 | return
756 | }
757 |
758 | logger $FriendlyName "Checking for existing Factory VM";
759 |
760 | # Check if there is already a factory VM - and kill it if there is
761 | if ((Get-VM | ? Name -eq $factoryVMName).Count -gt 0)
762 | {
763 | Stop-VM $factoryVMName -TurnOff -Confirm:$false -Passthru | Remove-VM -Force;
764 | }
765 |
766 | # Check for a base VHD
767 | if (-not (test-path $baseVHD))
768 | {
769 | if (-not (Test-Path $ISOFile)) {
770 | logger $FriendlyName 'ISO/WIM file missing, skipping this product.'
771 | return
772 | }
773 |
774 | # No base VHD - we need to create one
775 | logger $FriendlyName "No base VHD!";
776 |
777 | # Make unattend file
778 | logger $FriendlyName "Creating unattend file for base VHD";
779 |
780 | # Logon count is just "large number"
781 | makeUnattendFile -key $ProductKey -logonCount "1000" -filePath "$($workingDir)\unattend.xml" -desktop $desktop -is32bit $is32bit;
782 |
783 | # Time to create the base VHD
784 | logger $FriendlyName "Create base VHD using Convert-WindowsImage.ps1";
785 | $ConvertCommand = "Convert-WindowsImage";
786 | $ConvertCommand = $ConvertCommand + " -SourcePath `"$ISOFile`" -VHDPath `"$baseVHD`"";
787 | $ConvertCommand = $ConvertCommand + " -SizeBytes 80GB -VHDFormat $VHDFormat -UnattendPath `"$($workingDir)\unattend.xml`"";
788 | $ConvertCommand = $ConvertCommand + " -Edition $SKUEdition -VHDPartitionStyle $VHDPartitionStyle";
789 |
790 | Invoke-Expression "& $ConvertCommand";
791 |
792 | # Clean up unattend file - we don't need it any more
793 | logger $FriendlyName "Remove unattend file now that that is done";
794 | cleanupFile "$($workingDir)\unattend.xml";
795 |
796 | logger $FriendlyName "Mount VHD and copy bits in, also set startup file";
797 | MountVHDandRunBlock $baseVHD {
798 | cleanupFile -file "$($driveLetter):\Convert-WindowsImageInfo.txt";
799 |
800 | # Copy bits to VHD
801 | Copy-Item "$($ResourceDirectory)\bits" -Destination ($driveLetter + ":\") -Recurse;
802 |
803 | # Create first logon script
804 | Set-UpdateCheckPlaceHolders | Out-File -FilePath "$($driveLetter):\Bits\Logon.ps1" -Width 4096;
805 |
806 | # Disable Windows 10 (todo: check if Windows 10 - but wont break stuff on non-Win 10 right?)
807 | & reg load HKLM\NewOS "$($driveLetter):\windows\system32\config\SOFTWARE"
808 | & reg add HKLM\NewOS\Policies\Microsoft\WindowsStore /v AutoDownload /t REG_DWORD /d 2 /f
809 | & reg unload HKLM\NewOS
810 | }
811 |
812 | logger $FriendlyName "Create virtual machine, start it and wait for it to stop...";
813 | createRunAndWaitVM $baseVHD $Gen;
814 |
815 | # Remove Page file
816 | logger $FriendlyName "Removing the page file";
817 | MountVHDandRunBlock $baseVHD {
818 | attrib -s -h "$($driveLetter):\pagefile.sys";
819 | cleanupFile "$($driveLetter):\pagefile.sys";
820 | }
821 |
822 | # Compact the base file
823 | logger $FriendlyName "Compacting the base file";
824 | Optimize-VHD -Path $baseVHD -Mode Full;
825 | }
826 | else
827 | {
828 | # The base VHD existed - time to check if it needs an update
829 | logger $FriendlyName "Base VHD exists - need to check for updates";
830 |
831 | # create new diff to check for updates
832 | logger $FriendlyName "Create new differencing disk to check for updates";
833 | cleanupFile $updateVHD;
834 | New-VHD -Path $updateVHD -ParentPath $baseVHD | Out-Null;
835 |
836 | logger $FriendlyName "Copy login file for update check, also make sure flag file is cleared"
837 | MountVHDandRunBlock $updateVHD {
838 | # Refresh the Bits folder
839 | cleanupFile "$($driveLetter):\Bits"
840 | Copy-Item "$($ResourceDirectory)\bits" -Destination ($driveLetter + ":\") -Recurse;
841 | # Create the update check logon script
842 | Set-UpdateCheckPlaceHolders | Out-File -FilePath "$($driveLetter):\Bits\Logon.ps1" -Width 4096;
843 | }
844 |
845 | logger $FriendlyName "Create virtual machine, start it and wait for it to stop...";
846 | createRunAndWaitVM $updateVHD $Gen;
847 |
848 | # Mount the VHD
849 | logger $FriendlyName "Mount the differencing disk";
850 | $driveLetter = (Mount-VHD $updateVHD -Passthru | Get-Disk | Get-Partition | Get-Volume).DriveLetter;
851 |
852 | # Check to see if changes were made
853 | logger $FriendlyName "Check to see if there were any updates";
854 | if (Test-Path "$($driveLetter):\Bits\changesMade.txt")
855 | {
856 | cleanupFile "$($driveLetter):\Bits\changesMade.txt";
857 | logger $FriendlyName "Updates were found";
858 | }
859 | else
860 | {
861 | logger $FriendlyName "No updates were found";
862 | $sysprepNeeded = $false;
863 | }
864 |
865 | # Dismount
866 | logger $FriendlyName "Dismount the differencing disk";
867 | Dismount-VHD $updateVHD;
868 |
869 | # Wait 2 seconds for activity to clean up
870 | Start-Sleep -Seconds 2;
871 |
872 | # If changes were made - merge them in. If not, throw it away
873 | if ($sysprepNeeded)
874 | {
875 | logger $FriendlyName "Merge the differencing disk";
876 | Merge-VHD -Path $updateVHD -DestinationPath $baseVHD;
877 | }
878 | else
879 | {
880 | logger $FriendlyName "Delete the differencing disk";
881 | CSVLogger $finalVHD;
882 | cleanupFile $updateVHD;
883 | }
884 | }
885 |
886 | # Final Check - if the final VHD is missing - we need to sysprep and make it
887 | if (-not (Test-Path $finalVHD))
888 | {
889 | $sysprepNeeded = $true;
890 | }
891 |
892 | if ($sysprepNeeded)
893 | {
894 | # create new diff to sysprep
895 | logger $FriendlyName "Need to run Sysprep";
896 | logger $FriendlyName "Creating differencing disk";
897 | cleanupFile $sysprepVHD; new-vhd -Path $sysprepVHD -ParentPath $baseVHD | Out-Null;
898 |
899 | logger $FriendlyName "Mount the differencing disk and copy in files";
900 | MountVHDandRunBlock $sysprepVHD {
901 | $sysprepScriptBlockString = $sysprepScriptBlock | Out-String;
902 |
903 | if($GenericSysprep)
904 | {
905 | $sysprepScriptBlockString = $sysprepScriptBlockString.Replace(' `/unattend:"$unattendedXmlPath"', "");
906 | }
907 | else
908 | {
909 | # Make unattend file
910 | if (-not (Test-Path "$($driveLetter):\Bits")) {
911 | New-Item "$($driveLetter):\Bits" -ItemType Directory | Out-Null
912 | }
913 |
914 | makeUnattendFile -key $ProductKey -logonCount "1" -filePath "$($driveLetter):\Bits\unattend.xml" -desktop $desktop -is32bit $is32bit;
915 | }
916 |
917 | # Make the logon script
918 | cleanupFile "$($driveLetter):\Bits\Logon.ps1";
919 | $sysprepScriptBlockString | Out-File -FilePath "$($driveLetter):\Bits\Logon.ps1" -Width 4096;
920 | }
921 |
922 | logger $FriendlyName "Create virtual machine, start it and wait for it to stop...";
923 | createRunAndWaitVM $sysprepVHD $Gen;
924 |
925 | logger $FriendlyName "Mount the differencing disk and cleanup files";
926 | MountVHDandRunBlock $sysprepVHD {
927 | cleanupFile "$($driveLetter):\Bits\unattend.xml";
928 | cleanupFile "$($driveLetter):\Bits\Logon.ps1";
929 |
930 | if(-not $GenericSysprep)
931 | {
932 | # Make the logon script
933 | $postSysprepScriptBlock | Out-String | Out-File -FilePath "$($driveLetter):\Bits\Logon.ps1" -Width 4096;
934 | }
935 | else
936 | {
937 | # Cleanup \Bits as the postSysprepScriptBlock is not run anymore
938 | cleanupFile "$($driveLetter):\Bits";
939 | }
940 | }
941 |
942 | # Remove Page file
943 | logger $FriendlyName "Removing the page file";
944 | MountVHDandRunBlock $sysprepVHD {
945 |
946 | attrib -s -h "$($driveLetter):\pagefile.sys";
947 | cleanupFile "$($driveLetter):\pagefile.sys";
948 | }
949 |
950 | # Produce the final disk
951 | cleanupFile $finalVHD;
952 | logger $FriendlyName "Convert differencing disk into pristine base image";
953 | Convert-VHD -Path $sysprepVHD -DestinationPath $finalVHD -VHDType Dynamic;
954 | if($CleanWinSXS) {
955 | logger $FriendlyName "Cleaning windows component store. Be patient, this may take awhile."
956 | MountVHDandRunBlock $finalVHD {
957 | # Clean up the WinSXS store, and remove any superceded components. Updates will no longer be able to be uninstalled,
958 | # but saves a considerable amount of disk space.
959 | dism.exe /image:$($driveLetter):\ /Cleanup-Image /StartComponentCleanup /ResetBase
960 | }
961 | }
962 | logger $FriendlyName "Optimizing VHD file"
963 | # Mounting the VHD read only allows it to be compacted better.
964 | # Running Optimize-VHD twice seems to be necessary - don't know why, but it works.
965 | MountVHDandRunBlock -ReadOnly $finalVHD {
966 | Optimize-VHD $finalVHD -Mode Full
967 | Optimize-VHD $finalVHD -Mode Full
968 | }
969 | logger $FriendlyName "Delete differencing disk";
970 | CSVLogger $finalVHD -sysprepped;
971 | cleanupFile $sysprepVHD;
972 | }
973 | }
974 |
975 | function Process-ImagesFile {
976 | if (-not (Test-Path -Path $images -PathType Leaf)) {
977 | Write-Host -ForegroundColor Green "CSV Image file not found: $images"
978 | Throw 'Missing images csv file'
979 | }
980 |
981 | <#
982 | .SYNOPSIS
983 | Creates a friendly name for the target VM.
984 |
985 | .DESCRIPTION
986 | This function returns a string friendly name for the VM based on the target parameters.
987 | If the target specified a name explicitly, that name is returned. Otherwise, the name will be generated
988 | using this pattern:
989 |
990 | [Core | with GUI] [- Gen 2]
991 | [- 32 bit] [- Gen 2]
992 | [- Gen 2]
993 |
994 | .PARAMETER target
995 | Specifies the target configuration.
996 |
997 | .PARAMETER gui
998 | Specifies if the VM OS will be GUI OS. If not GUI, it will be a Core OS.
999 |
1000 | .PARAMETER generation
1001 | Specifies the Hyper-V VM generation. 1 or 2.
1002 | #>
1003 | function Create-FriendlyName ($target, [bool]$gui, [int]$generation) {
1004 | if ($target.Name -ne "") {
1005 | # use the user supplied friendly name
1006 | return $target.Name
1007 | } else {
1008 | # Build FriendlyName compatible with existing system
1009 | #
1010 | # [Core | with GUI] [- Gen 2]
1011 | # [- 32 bit] [- Gen 2]
1012 | # [- Gen 2]
1013 | $friendlyName = $target.Product + " " + $target.Edition
1014 |
1015 | switch ($target.Edition)
1016 | {
1017 | "DataCenter" {
1018 | if ($gui) {
1019 | $friendlyName += " with GUI";
1020 | } else {
1021 | $friendlyName += " Core";
1022 | }
1023 | }
1024 |
1025 | "Standard" {
1026 | if ($gui) {
1027 | $friendlyName += " with GUI";
1028 | } else {
1029 | $friendlyName += " Core";
1030 | }
1031 | }
1032 |
1033 | "Ultimate" {
1034 | }
1035 |
1036 | "Enterprise" {
1037 | # only Enterprise has servicing branches
1038 | if ($target.SB -ne "") {
1039 | $friendlyName += " " + $target.SB
1040 | }
1041 | }
1042 |
1043 | "Professional" {
1044 | }
1045 | }
1046 |
1047 | if ($target.Arch -eq "x86") {
1048 | $friendlyName += " - 32 bit";
1049 | }
1050 |
1051 | if ($generation -eq 2) {
1052 | $friendlyName += " - Gen 2"
1053 | }
1054 |
1055 | return $friendlyName
1056 | }
1057 | }
1058 |
1059 | function Create-Arguments ($target, [bool]$gui, [int]$generation) {
1060 | $arguments = @{}
1061 |
1062 | $arguments.Add("ISOFile", $target.Image)
1063 | $arguments.Add("ProductKey", $target.ProductKey)
1064 |
1065 | #
1066 | $friendlyName = Create-FriendlyName -target $target -gui $gui -generation $generation
1067 | $arguments.Add("FriendlyName", $friendlyName)
1068 |
1069 | switch ($target.Edition)
1070 | {
1071 | "DataCenter" {
1072 | if ($gui) {
1073 | $arguments.Add("SKUEdition", "Server" + $target.Edition)
1074 | } else {
1075 | $arguments.Add("SKUEdition", "Server"+ $target.Edition+"Core")
1076 | }
1077 | }
1078 |
1079 | "Standard" {
1080 | if ($gui) {
1081 | $arguments.Add("SKUEdition", "Server" + $target.Edition)
1082 | } else {
1083 | $arguments.Add("SKUEdition", "Server"+ $target.Edition+"Core")
1084 | }
1085 | }
1086 |
1087 | "Ultimate" {
1088 | $arguments.Add("SKUEdition", $target.Edition)
1089 | $arguments.Add("desktop", $true)
1090 | }
1091 |
1092 | "Enterprise" {
1093 | $arguments.Add("SKUEdition", $target.Edition)
1094 | $arguments.Add("desktop", $true)
1095 | }
1096 |
1097 | "Professional" {
1098 | $arguments.Add("SKUEdition", $target.Edition)
1099 | $arguments.Add("desktop", $true)
1100 | }
1101 | }
1102 |
1103 | if ($target.Arch -eq "x86") {
1104 | }
1105 |
1106 | if ($generation -eq 2) {
1107 | $arguments.Add("Generation2", $true)
1108 | }
1109 |
1110 |
1111 | if ($target.Arch -eq "x86") {
1112 | $arguments.Add("Is32Bit", $true)
1113 | }
1114 |
1115 | return $arguments;
1116 | }
1117 |
1118 | <#
1119 | .SYNOPSIS
1120 | Creates zero or more arguments to be passed to Start-ImageFactory
1121 |
1122 | .DESCRIPTION
1123 | This function returns an array of arguments that can be passed to Start-ImageFactory.
1124 | The number of return values depends if the configuration specified a GUI, Core, Generation 1 or Generation 2 VM.
1125 |
1126 | .PARAMETER target
1127 | Specifies the target configuration.
1128 | #>
1129 | function Create-StartImageFactoryArguments ($target) {
1130 | $arguments = @()
1131 |
1132 | if ($target.GUI -eq "TRUE") {
1133 |
1134 | if ($target.Gen1 -eq "TRUE") {
1135 | $arguments += Create-Arguments -target $target -gui $true -generation 1
1136 | }
1137 |
1138 | if ($target.Gen2 -eq "TRUE") {
1139 | $arguments += Create-Arguments -target $target -gui $true -generation 2
1140 | }
1141 | }
1142 |
1143 | if ($target.Core -eq "TRUE") {
1144 |
1145 | if ($target.Gen1 -eq "TRUE") {
1146 | $arguments += Create-Arguments -target $target -gui $false -generation 1
1147 | }
1148 |
1149 | if ($target.Gen2 -eq "TRUE") {
1150 | $arguments += Create-Arguments -target $target -gui $false -generation 2
1151 | }
1152 | }
1153 |
1154 | return $arguments;
1155 | }
1156 |
1157 | <#
1158 | .SYNOPSIS
1159 | Gets the full path to image in the specified path.
1160 |
1161 | .DESCRIPTION
1162 | This function returns the full path or $null if the specified file was not found.
1163 |
1164 | .PARAMETER image
1165 | Specifies the image file name to find.
1166 |
1167 | .PARAMETER path
1168 | Specifies a path to search.
1169 | #>
1170 | function Get-Image($image, $path) {
1171 | if (Test-Path $path -PathType Container) {
1172 | $item = Get-ChildItem -Path $path -Filter $image -Recurse
1173 | if ($item -ne $null) {
1174 | return $item.FullName
1175 | }
1176 | }
1177 |
1178 | return $null
1179 | }
1180 |
1181 | $targets = Import-Csv -Path $images
1182 | ForEach ($target In $targets) {
1183 |
1184 | if ($target.Skip -eq "TRUE" -or $target.Image -eq "") {
1185 | #Write-Host -ForegroundColor Green "Skipping $($target)"
1186 | continue;
1187 | }
1188 |
1189 | # search in the working ISOs directory if exists
1190 | if (-not (Test-Path -Path $target.Image -PathType Leaf)) {
1191 | $workingIsoDir = [io.path]::Combine($workingDir,"ISOs")
1192 | $image = Get-Image -image $target.Image -path $workingIsoDir
1193 | if ($image -ne $null) {
1194 | $target.Image = $image
1195 | }
1196 | }
1197 |
1198 | # search in the iso search directory if specified
1199 | if (-not (Test-Path -Path $target.Image -PathType Leaf)) {
1200 | $image = Get-Image -image $target.Image -path $isoSearchDir
1201 | if ($image -ne $null) {
1202 | $target.Image = $image
1203 | }
1204 | }
1205 |
1206 | if (-not (Test-Path -Path $target.Image -PathType Leaf)) {
1207 | $iso = $target.Image
1208 | $gui = $target.Core -ne "TRUE"
1209 | $friendlyName = Create-FriendlyName -target $target -gui $gui -generation $generation
1210 | Write-Host -ForegroundColor Green "ISO/WIM not found [$friendlyName]: $iso"
1211 | continue;
1212 | }
1213 |
1214 | # one target could create Gen 1/Gen 2/Core/Gui variations
1215 | $arguments = Create-StartImageFactoryArguments -target $target
1216 | ForEach ($arg In $arguments) {
1217 | Start-ImageFactory @arg
1218 | }
1219 | }
1220 | }
1221 |
1222 | if($startfactory) {
1223 | if ($images -ne $null) {
1224 | Process-ImagesFile
1225 | } else {
1226 | # process original way
1227 | Start-ImageFactory -FriendlyName "Windows Server 2016 DataCenter Core - Gen 2" -ISOFile $2016Image -ProductKey $Windows2016Key -SKUEdition "ServerDataCenterCore" -Generation2;
1228 | Start-ImageFactory -FriendlyName "Windows Server 2016 DataCenter with GUI - Gen 2" -ISOFile $2016Image -ProductKey $Windows2016Key -SKUEdition "ServerDataCenter" -Generation2;
1229 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter with GUI" -ISOFile $2012R2Image -ProductKey $Windows2012R2Key -SKUEdition "ServerDataCenter";
1230 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter Core" -ISOFile $2012R2Image -ProductKey $Windows2012R2Key -SKUEdition "ServerDataCenterCore";
1231 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter with GUI - Gen 2" -ISOFile $2012R2Image -ProductKey $Windows2012R2Key -SKUEdition "ServerDataCenter" -Generation2;
1232 | Start-ImageFactory -FriendlyName "Windows Server 2012 R2 DataCenter Core - Gen 2" -ISOFile $2012R2Image -ProductKey $Windows2012R2Key -SKUEdition "ServerDataCenterCore" -Generation2;
1233 | Start-ImageFactory -FriendlyName "Windows Server 2012 DataCenter with GUI" -ISOFile $2012Image -ProductKey $Windows2012Key -SKUEdition "ServerDataCenter";
1234 | Start-ImageFactory -FriendlyName "Windows Server 2012 DataCenter Core" -ISOFile $2012Image -ProductKey $Windows2012Key -SKUEdition "ServerDataCenterCore";
1235 | Start-ImageFactory -FriendlyName "Windows Server 2012 DataCenter with GUI - Gen 2" -ISOFile $2012Image -ProductKey $Windows2012Key -SKUEdition "ServerDataCenter" -Generation2;
1236 | Start-ImageFactory -FriendlyName "Windows Server 2012 DataCenter Core - Gen 2" -ISOFile $2012Image -ProductKey $Windows2012Key -SKUEdition "ServerDataCenterCore" -Generation2;
1237 | Start-ImageFactory -FriendlyName "Windows Server 2008 R2 DataCenter with GUI" -ISOFile $2008R2Image -ProductKey $Windows2008R2Key -SKUEdition "ServerDataCenter";
1238 | Start-ImageFactory -FriendlyName "Windows Server 2008 R2 DataCenter Core" -ISOFile $2008R2Image -ProductKey $Windows2008R2Key -SKUEdition "ServerDataCenterCore";
1239 | Start-ImageFactory -FriendlyName "Windows 8.1 Professional" -ISOFile $81x64Image -ProductKey $Windows81Key -SKUEdition "Professional" -desktop $true;
1240 | Start-ImageFactory -FriendlyName "Windows 8.1 Professional - Gen 2" -ISOFile $81x64Image -ProductKey $Windows81Key -SKUEdition "Professional" -Generation2 -desktop $true;
1241 | Start-ImageFactory -FriendlyName "Windows 8.1 Professional - 32 bit" -ISOFile $81x86Image -ProductKey $Windows81Key -SKUEdition "Professional" -desktop $true -is32bit $true;
1242 | Start-ImageFactory -FriendlyName "Windows 8 Professional" -ISOFile $8x64Image -ProductKey $Windows8Key -SKUEdition "Professional" -desktop $true;
1243 | Start-ImageFactory -FriendlyName "Windows 8 Professional - Gen 2" -ISOFile $8x64Image -ProductKey $Windows8Key -SKUEdition "Professional" -Generation2 -desktop $true;
1244 | Start-ImageFactory -FriendlyName "Windows 8 Professional - 32 bit" -ISOFile $8x86Image -ProductKey $Windows8Key -SKUEdition "Professional" -desktop $true -is32bit $true;
1245 | Start-ImageFactory -FriendlyName "Windows 7 Enterprise" -ISOFile $7x64Image -ProductKey $Windows7Key -SKUEdition "Enterprise" -desktop $true;
1246 | Start-ImageFactory -FriendlyName "Windows 7 Enterprise - 32 bit" -ISOFile $7x86Image -ProductKey $Windows7Key -SKUEdition "Enterprise" -desktop $true -is32bit $true;
1247 | Start-ImageFactory -FriendlyName "Windows 10 Professional" -ISOFile $10x64Image -ProductKey $Windows10Key -SKUEdition "Professional" -desktop $true;
1248 | Start-ImageFactory -FriendlyName "Windows 10 Professional - Gen 2" -ISOFile $10x64Image -ProductKey $Windows10Key -SKUEdition "Professional" -Generation2 -desktop $true;
1249 | Start-ImageFactory -FriendlyName "Windows 10 Professional - 32 bit" -ISOFile $10x86Image -ProductKey $Windows10Key -SKUEdition "Professional" -desktop $true -is32bit $true;
1250 | }
1251 | } else {
1252 | If($myinvocation.InvocationName -eq '.') {
1253 | Write-Host 'Start-ImageFactory is ready to use'
1254 | } else {
1255 | Write-Host 'Image Factory is set to be dot sourced. If you want to run this script directly, changed $startfactory to $true in FactoryVariables.ps1'
1256 | }
1257 | }
1258 |
--------------------------------------------------------------------------------
/Image-Factory/FactoryVariables.ps1:
--------------------------------------------------------------------------------
1 | # Seperate file for variables - this when you pull down the latest factory file you can keep your paths / product keys etc...
2 | $workingDir = "C:\FactoryTest"
3 | $logFile = "$($workingDir)\Share\Details.csv"
4 | $ResourceDirectory = "$($workingDir)\Resources"
5 | $Organization = "The Power Elite"
6 | $Owner = "Ben Armstrong"
7 | $Timezone = "Pacific Standard Time"
8 | $adminPassword = "P@ssw0rd"
9 | $userPassword = "P@ssw0rd"
10 |
11 | # optional. string or string[]
12 | # if using image file, this dir will be recursively search for images only if image not found in $workingDir\ISOs
13 | #$isoSearchDir = "C:\msdn"
14 |
15 | # VM Settings
16 | # Name of the VM - make sure this does not conflict with any existing virtual machine, because it gets deleted automatically!
17 | $factoryVMName = "Factory VM"
18 |
19 | # Amount of memory to give the VM - more memory usually makes updates install faster
20 | $VMMemory = 2048mb
21 |
22 | # Virtual switch to connect to - needs internet access to download updates
23 | $virtualSwitchName = "Virtual Switch"
24 |
25 | # Set to $true and specify VLAN id to connect the VM to a specific VLAN
26 | $UseVLAN = $false
27 | $VlanId = 1
28 |
29 | # Enable to use a static IP address for the VM. Default is to use DHCP
30 | $UseStaticIP = $false
31 | $IP = '10.10.10.10'
32 | $MaskBits = 24
33 | $Gateway = '10.10.10.1'
34 | $Dns = '10.10.10.2'
35 | $IPType = 'IPv4'
36 |
37 |
38 | # Keys
39 | $Windows10Key = ""
40 | $Windows7Key = ""
41 | $Windows2008R2Key = ""
42 |
43 | $Windows81Key = ""
44 | $Windows2012R2Key = ""
45 |
46 | $Windows8Key = ""
47 | $Windows2012Key = ""
48 |
49 | $Windows2016Key = ""
50 |
51 | # ISOs / WIMs
52 | $2016Image = "$($workingDir)\ISOs\en_windows_server_2016_technical_preview_4_x64_dvd_7258292.iso"
53 | $2008R2Image = "$($workingDir)\ISOs\en_windows_server_2008_r2_standard_enterprise_datacenter_and_web_with_sp1_x64_dvd_617601.wim"
54 | $2012Image = "$($workingDir)\ISOs\en_windows_server_2012_x64_dvd_915478.wim"
55 | $2012R2Image = "$($workingDir)\ISOs\en_windows_server_2012_r2_x64_dvd_2707946.wim"
56 | $7x86Image = "$($workingDir)\ISOs\en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.wim"
57 | $7x64Image = "$($workingDir)\ISOs\en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.wim"
58 | $8x86Image = "$($workingDir)\ISOs\en_windows_8_x86_dvd_915417.wim"
59 | $8x64Image = "$($workingDir)\ISOs\en_windows_8_x64_dvd_915440.wim"
60 | $81x86Image = "$($workingDir)\ISOs\en_windows_8_1_x86_dvd_2707392.wim"
61 | $81x64Image = "$($workingDir)\ISOs\en_windows_8_1_x64_dvd_2707217.wim"
62 | $10x86Image = "$($workingDir)\ISOs\en_windows_10_multiple_editions_x86_dvd_6848465.wim"
63 | $10x64Image = "$($workingDir)\ISOs\en_windows_10_multiple_editions_x64_dvd_6846432.wim"
64 |
65 | # Misc Settings
66 | # If $startfactory is false, just import the functions to use in other scripts. If true, start image generation.
67 | $StartFactory = $true
68 |
69 | # If enabled, will use dism.exe to clean the WinSXS folder and remove old updates. This can give significant disk savings, but takes longer (20-30 minutes per image). Updates will not be able to be uninstalled if this is enabled.
70 | $CleanWinSXS = $false
71 |
72 | # If enabled, will use the legacy VHD format rather than VHDX to support Hyper-V before Windows Server 2012
73 | $LegacyVHD = $false
74 |
--------------------------------------------------------------------------------
/Image-Factory/images.csv:
--------------------------------------------------------------------------------
1 | Name,Product,Edition,SB,Arch,GUI,Core,Gen1,Gen2,ProductKey,Image,SHA1,Version,Sort,Notes
2 | ,Windows Server 2016,DataCenter,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2016_x64_dvd_9327751.iso,7E33DF150CB4D1FE3503E23433A1867DDD79A6EE,10,1,
3 | ,Windows Server 2016,Standard,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2016_x64_dvd_9327751.iso,7E33DF150CB4D1FE3503E23433A1867DDD79A6EE,10,1,
4 | ,Windows Server 2012 R2,DataCenter,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso,865494E969704BE1C4496D8614314361D025775E,6.3,1,
5 | ,Windows Server 2012 R2,Standard,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso,865494E969704BE1C4496D8614314361D025775E,6.3,1,
6 | ,Windows Server 2012,DataCenter,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2012_x64_dvd_915478.iso,D09E752B1EE480BC7E93DFA7D5C3A9B8AAC477BA,6.2,1,
7 | ,Windows Server 2012,Standard,,x64,TRUE,FALSE,FALSE,TRUE,,en_windows_server_2012_x64_dvd_915478.iso,D09E752B1EE480BC7E93DFA7D5C3A9B8AAC477BA,6.2,1,
8 | ,Windows Server 2008 R2,DataCenter,,x64,TRUE,FALSE,FALSE,FALSE,,en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso,D3FD7BF85EE1D5BDD72DE5B2C69A7B470733CD0A,6.1,1,
9 | ,Windows Server 2008 R2,Standard,,x64,TRUE,FALSE,FALSE,FALSE,,en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso,D3FD7BF85EE1D5BDD72DE5B2C69A7B470733CD0A,6.1,1,
10 | ,Windows Server 2008 R2,Enterprise,,x64,TRUE,FALSE,FALSE,FALSE,,en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso,D3FD7BF85EE1D5BDD72DE5B2C69A7B470733CD0A,6.1,1,
11 | ,Windows 10,Enterprise,,x86,TRUE,,FALSE,FALSE,,en_windows_10_enterprise_x86_dvd_6851156.iso,E7138032986BFCFA0E5F1A8E41E2E9FD1EC94268,10,2,
12 | ,Windows 10,Enterprise,,x64,TRUE,,FALSE,TRUE,,en_windows_10_enterprise_x64_dvd_6851151.iso,30AD1CDF5D0670F12788005131E24862F6AB8AAB,10,2,
13 | ,Windows 10,Enterprise,2015 LTSB,x64,TRUE,,FALSE,TRUE,,en_windows_10_enterprise_2015_ltsb_x64_dvd_6848446.iso,264D48C902E6B586A1ED965062F7DA4D4DA99B35,10,2,
14 | ,Windows 10,Enterprise,2015 LTSB,x86,TRUE,,FALSE,FALSE,,en_windows_10_enterprise_2015_ltsb_x86_dvd_6848454.iso,ABE400AD86C604197A3B163E31AFCC1B7C53325E,10,2,
15 | ,Windows 10,Enterprise,2016 LTSB,x64,TRUE,,FALSE,TRUE,,en_windows_10_enterprise_2016_ltsb_x64_dvd_9059483.iso,031ED6ACDC47B8F582C781B039F501D83997A1CF,10,2,
16 | ,Windows 10,Enterprise,2016 LTSB,x86,TRUE,,FALSE,FALSE,,en_windows_10_enterprise_2016_ltsb_x86_dvd_9060010.iso,45E72D02FF17125C699558719EB946D8E140C9CC,10,2,
17 | ,Windows 10,Enterprise,1607,x64,TRUE,,FALSE,TRUE,,en_windows_10_enterprise_version_1607_updated_jul_2016_x64_dvd_9054264.iso,F9FFEA3A40BF39CCDE105BB064E153343560D73E,10,2,
18 | ,Windows 10,Enterprise,1607,x86,TRUE,,FALSE,FALSE,,en_windows_10_enterprise_version_1607_updated_jul_2016_x86_dvd_9060097.iso,89E415FEC5F4ED23D96CB543D3CEA838F57A10A0,10,2,
19 | ,Windows 10,Professional,1607,x64,TRUE,,FALSE,TRUE,,en_windows_10_multiple_editions_version_1607_updated_jul_2016_x64_dvd_9058187.iso,99FD8082A609997AE97A514DCA22BECF20420891,10,2,
20 | ,Windows 10,Professional,1607,x86,TRUE,,FALSE,FALSE,,en_windows_10_multiple_editions_version_1607_updated_jul_2016_x86_dvd_9053863.iso,5A4D2C953D884578A6ADB448BEE5753B80EC1CFA,10,2,
21 | ,Windows 10,Professional,,x64,TRUE,,FALSE,TRUE,,en_windows_10_multiple_editions_version_1511_updated_apr_2016_x64_dvd_8705583.iso,1B247B5B348E78C9BC3AFD3C1CBE10CEE3D1B9D5,10,2,
22 | ,Windows 10,Professional,,x86,TRUE,,FALSE,FALSE,,en_windows_10_multiple_editions_version_1511_updated_apr_2016_x86_dvd_8735185.iso,2DDE3A5BFA60B7F10CD38DBD544CF00A075F8908,10,2,
23 | ,Windows 8.1,Professional,with Update,x64,TRUE,,FALSE,FALSE,,,,6.3,2,
24 | ,Windows 8.1,Professional,with Update,x86,TRUE,,FALSE,FALSE,,,,6.3,2,
25 | ,Windows 8.1,Professional,,x64,TRUE,,FALSE,FALSE,,,,6.3,2,
26 | ,Windows 8.1,Professional,,x86,TRUE,,FALSE,FALSE,,,,6.3,2,
27 | ,Windows 8.1,Enterprise,,x64,TRUE,,FALSE,FALSE,,en_windows_8_1_enterprise_x64_dvd_2971902.iso,AE792B2EF982DAC7391224B624EAB8D6340D78AB,6.3,2,
28 | ,Windows 8.1,Enterprise,,x86,TRUE,,FALSE,FALSE,,en_windows_8_1_enterprise_x86_dvd_2972289.iso,5EFB981C94E1223C85F3BB504D04E0642A85C1D4,6.3,2,
29 | ,Windows 8,Enterprise,,x64,TRUE,,FALSE,FALSE,,en_windows_8_enterprise_x64_dvd_917522.iso,4EADFE83E736621234C63E8465986F0AF6AA3C82,6.2,2,
30 | ,Windows 8,Professional,,x64,TRUE,,FALSE,FALSE,,,,6.2,2,
31 | ,Windows 7,Professional,,x64,TRUE,,FALSE,FALSE,,en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso,0BCFC54019EA175B1EE51F6D2B207A3D14DD2B58,6.1,2,
32 | ,Windows 7,Ultimate,,x64,TRUE,,FALSE,FALSE,,en_windows_7_ultimate_with_sp1_x64_dvd_u_677332.iso,36AE90DEFBAD9D9539E649B193AE573B77A71C83,6.1,2,
33 | ,Windows 7,Ultimate,,x86,TRUE,,FALSE,FALSE,,en_windows_7_ultimate_with_sp1_x86_dvd_u_677460.iso,65FCE0F445D9BF7E78E43F17E441E08C63722657,6.1,2,
34 | ,Windows 7,Professional,,x64,TRUE,,FALSE,FALSE,,en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso,36AE90DEFBAD9D9539E649B193AE573B77A71C83,6.1,2,
35 | ,Windows 7,Professional,,x86,TRUE,,FALSE,FALSE,,en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso,D89937DF3A9BC2EC1A1486195FD308CD3DADE928,6.1,2,
36 |
--------------------------------------------------------------------------------
/Image-Factory/images.md:
--------------------------------------------------------------------------------
1 | # Images CSV File
2 |
3 | The images CSV file is used to manage the various Windows products, editions and options for running Image Factory. Each row defines a Windows product, edition and processor architecture.
4 |
5 | ## File Format
6 |
7 |
8 | ### Columns
9 |
10 | | Column | Description |
11 | | ------------- | -------------------- |
12 | | **Name** | Optional. Used as the file name for the image. If not specified, the Product, Edition, SB, Arch, GUI/Core and Gen1/Gen2 will be used to generate a name.
13 | | **Product** | The name of the Windows product without the edition, for example 'Windows 10'.
14 | | **Edition** | The edition such as DataCenter, Standard, Enterprise, Professional, etc
15 | | **SB** | Servicing Branch or release (such as *1511* or *1607* for Windows 10, LTSB)
16 | | **Arch** | x64 or x86. Used to set Is32Bit parameter. Needed to create the unattend file correctly.
17 | | **GUI** | TRUE/FALSE to install GUI version. For non-server OS, this must be TRUE.
18 | | **Core** | TRUE/FALSE to install Core version. Only applies to server products that support Core deployments.
19 | | **Gen1** | Create a Generation 1 virtual machine.
20 | | **Gen2** | Create a Generation 2 virtual machine.
21 | | **ProductKey** | The product key to use for the unattended installation.
22 | | **Image** | The ISO or WIM file to use as the base of the windows image.
23 | | **SHA1** | The SHA1 hash of Image. Not used by Image Factory. Could be used in future to validate ISO before installing.
24 | | **Version** | [Version of Windows](https://en.wikipedia.org/wiki/Windows_NT). Used to sort file. Not used by Image Factory. Only machines with version of 6.2 or higher can use secure boot.
25 | | **Sort** | User field. Helper column used to help sorting when using Excel to edit.
26 | | **Notes** | User field. User specific notes. Not used by Image Factory.
27 |
--------------------------------------------------------------------------------
/Image-Factory/readme.md:
--------------------------------------------------------------------------------
1 | # Hyper-V Image Factory #
2 |
3 | This is a PowerShell script that creates and maintains a set of Windows virtual hard disks for me that are always up to date.
4 |
5 | # What is this all about? #
6 |
7 | For more information - read here: http://blogs.msdn.com/b/virtual_pc_guy/archive/2015/06/16/script-image-factory-for-hyper-v.aspx
8 |
9 |
10 | # Change Log #
11 | 9/3/16 -
12 | * Changes from Phil Bolduc
13 | * optionally load target images from a CSV file using a command line argument (-images)
14 | * optionally load variables from different files using a command line argument (-variables)
15 |
16 | 10/30/15 -
17 | * Changes from Garry Martin
18 | * Add support for Windows 7 and Windows Server 2008 R2 by installing WMF and .NET Framework
19 | * Add LegacyVHD parameter to allow legacy VHD format files to be created instead of VHDX files
20 | * Change behaviour for removal of autorun registry key
21 | * Clean up Temp directory and Demo user if created
22 |
23 | 8/21/15 -
24 | * Changes from Grant Emsley
25 | * Support configuration of static IP address on the factory VM
26 | * Verify product keys have been set
27 |
28 | 8/17/15 -
29 | * Changes from Grant Emsley
30 | * Optimize final VHD file
31 | * Optionally clean the windows component store to save disk space
32 |
33 | 8/16/15 -
34 |
35 | * Changes from Grant Emsley
36 | * Support for configuring Factory VM RAM and VLAN
37 | * Better error handling on missing folders / dependancies
38 | * Better handling of missing ISO / WIM files
39 |
40 | 8/1/15 -
41 |
42 | * Tested with Windows 10 (yay!)
43 | * Moved variables into separate file to make it easier to accept changes - and to stop people from giving me their product keys
44 | * Added code to check for and create directories that are missing
45 |
46 | 7/29/15 -
47 |
48 | * Accepted changes from Christoph Petersen that:
49 | * Cleaned up files inside the virtual machine at the end
50 | * Added support for an external sysprep process
51 | * Made the code a lot prettier to look at (thanks Christoph!)
52 |
53 | # To do #
54 |
55 | This is the list of things currently in my "to do" list. Feel free to tackle any of them yourself and request a pull.
56 |
57 | * MD5 summing for virtual hard drives in the share folder
58 | * Make variables for the build and share directories - so they do not have to be under the working directory
59 | * Update this readme to have more information from the blog post - and vice versa
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Benjamin Armstrong
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 |
--------------------------------------------------------------------------------
/Misc/BingCmdlets.ps1:
--------------------------------------------------------------------------------
1 | Add-Type -Assembly System.Web
2 | $WebClient = New-Object system.Net.WebClient
3 |
4 | Function Get-BingCount([string] $Term) {
5 |
6 | # Add plus and quotes, encodes for URL
7 | $Term = '+"' + $Term + '"'
8 | $Term = [System.Web.HttpUtility]::UrlEncode($Term)
9 |
10 | # Load the page as a string
11 | $URL = "http://www.bing.com/search?q=" + $Term
12 | $Page = $WebClient.DownloadString($URL)
13 |
14 | # searches for the string before the number of hits on the page
15 | $String1 = ''
16 | $Index1 = $Page.IndexOf($String1)
17 |
18 | # if found the right string, finds the exact end of the number
19 | If ($Index1 -ne -1) {
20 | $Index1 += $String1.Length
21 | $Index2 = $Page.IndexOf(" ", $Index1)
22 | $result = $Page.Substring($Index1, $Index2 - $index1)
23 | } else { $result = "0" }
24 |
25 | # Return the count
26 | return $result
27 | }
28 |
29 | $CmdletList = Get-Command -Module Hyper-V | Select Name
30 | $CmdletCount = $CmdletList.Count -1
31 | $hashTable = $null
32 | $hashTable = @{}
33 |
34 | 0..$CmdletCount | % {
35 |
36 | # Tracks progress
37 | Write-Progress -Activity "Checking cmdlet popularity" -PercentComplete ($_ * 100 / $CmdletCount)
38 |
39 | # Check the popularity with Bing
40 | $cmdlet = $CmdletList[$_].Name
41 | $count = [int] (Get-BingCount $cmdlet)
42 |
43 | # Put data in a hashtable
44 | $hashTable.add($cmdlet, $count)
45 | }
46 |
47 | $hashTable.GetEnumerator() | sort value -Descending | select @{N="Cmdlet";E={$_.Name}}, @{N="Popularity";E={$_.Value}}
48 |
49 | Write-Progress -Activity "Checking cmdlet popularity" -Completed
50 |
51 | # Releases resources used by the web client
52 | $WebClient.Dispose()
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hyper-V-PowerShell
2 | Scripts that primarily use the Hyper-V PowerShell interfaces driving virtual machines
3 |
--------------------------------------------------------------------------------
/Snippets/CopyTextFileIntoVM.ps1:
--------------------------------------------------------------------------------
1 | # Pass in script file - array trick from http://powershell.com/cs/forums/t/4169.aspx
2 | function copyTextFileIntoVM([string]$VMName, $cred, [string]$sourceFilePath, [string]$destinationFilePath){
3 | $content = Get-Content $sourceFilePath
4 | icm -VMName $VMName -Credential $cred {param($Script, $file)
5 | $script | set-content $file} -ArgumentList (,$content), $destinationFilePath}
--------------------------------------------------------------------------------
/Snippets/PSCreds.ps1:
--------------------------------------------------------------------------------
1 | $localCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Administrator", (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force)
--------------------------------------------------------------------------------
/Snippets/Readme.md:
--------------------------------------------------------------------------------
1 | # Snippets #
2 |
3 | Random snippets of useful PowerShell
--------------------------------------------------------------------------------
/Snippets/WaitForPSDirect.ps1:
--------------------------------------------------------------------------------
1 | function waitForPSDirect([string]$VMName, $cred){
2 | Write-Output "[$($VMName)]:: Waiting for PowerShell Direct (using $($cred.username))"
3 | while ((icm -VMName $VMName -Credential $cred {"Test"} -ea SilentlyContinue) -ne "Test") {Sleep -Seconds 1}}
--------------------------------------------------------------------------------
/Snippets/WaitForVMDHCP.ps1:
--------------------------------------------------------------------------------
1 | # Wait for DHCP
2 | while ((Get-NetIPAddress | ? AddressFamily -eq IPv4 | ? IPAddress -ne 127.0.0.1).SuffixOrigin -ne "Dhcp") {sleep -Milliseconds 10}
--------------------------------------------------------------------------------
/Snippets/addNicWithIP.ps1:
--------------------------------------------------------------------------------
1 | function addNicWithIP([string]$VMName,$cred, [string]$Switch, [string]$IPaddress, [string]$subnetPrefixLength){
2 | $newNetAdapter = Add-VMNetworkAdapter -VMName $VMName -SwitchName $Switch -Passthru
3 | Write-Output "[$($VMName)]:: Wait for IP address and create virtual switch"
4 | waitForPSDirect $VMName $cred
5 | icm -VMName $VMName -Credential $cred {
6 | param($IPAddress, $subnetPrefixLength, $MacAddress)
7 | # Wait for the NIC to appear
8 | do {sleep 1} until (@(get-netadapter | ? PermanentAddress -eq $MacAddress).Count -eq 1)
9 | # Setup IP Address
10 | New-NetIPAddress -IPAddress $IPAddress `
11 | -InterfaceAlias (get-netadapter | ? PermanentAddress -eq $MacAddress).Name `
12 | -PrefixLength $subnetPrefixLength
13 | } -ArgumentList $IPAddress, $subnetPrefixLength, $newNetAdapter.MacAddress
14 | }
--------------------------------------------------------------------------------
/Ubuntu-VM-Build/BaseUbuntuBuild.ps1:
--------------------------------------------------------------------------------
1 | $tempPath = [System.IO.Path]::GetTempPath() + [System.Guid]::NewGuid().ToString()
2 |
3 | # ADK Download - https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install
4 | # You only need to install the deployment tools
5 | $oscdimgPath = "${Env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
6 |
7 | # Download qemu-img from here: http://www.cloudbase.it/qemu-img-windows/
8 | $qemuImgPath = "${Env:UserProfile}\Downloads\qemu-img.exe"
9 |
10 | # Update this to the release of Ubuntu that you want
11 | $ubuntuPath = "http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64"
12 |
13 | $GuestOSName = "Hyper-V-VM"
14 | $GuestOSID = "iid-123456"
15 | $GuestAdminPassword = "P@ssw0rd"
16 |
17 | $VMName = "Ubuntu Test"
18 | $virtualSwitchName = "Virtual Switch"
19 |
20 | $vmPath = "${Env:Public}\Documents\Hyper-V\$VMName"
21 | $imageCachePath = $tempPath
22 | $vhdx = "$($vmPath)\test.vhdx"
23 | $metaDataIso = "$($vmPath)\metadata.iso"
24 |
25 | # Get the timestamp of the latest build on the Ubuntu cloud-images site
26 | $stamp = (Invoke-WebRequest "$($ubuntuPath).manifest").BaseResponse.LastModified.ToFileTimeUtc()
27 |
28 | $metadata = @"
29 | instance-id: $($GuestOSID)
30 | local-hostname: $($GuestOSName)
31 | "@
32 |
33 | $userdata = @"
34 | #cloud-config
35 | password: $($GuestAdminPassword)
36 | runcmd:
37 | - [ useradd, -m, -p, "", ben ]
38 | - [ chage, -d, 0, ben ]
39 | "@
40 |
41 | # Check Paths
42 | if (!(test-path $vmPath)) {mkdir $vmPath}
43 | if (!(test-path $imageCachePath)) {mkdir $imageCachePath}
44 |
45 | # Helper function for no error file cleanup
46 | Function cleanupFile ([string]$file) {if (test-path $file) {Remove-Item $file}}
47 |
48 | # Delete the VM if it is around
49 | If ((Get-VM | ? name -eq $VMName).Count -gt 0)
50 | {stop-vm $VMName -TurnOff -Confirm:$false -Passthru | Remove-VM -Force}
51 |
52 | cleanupFile $vhdx
53 | cleanupFile $metaDataIso
54 |
55 | # Make temp location
56 | md -Path $tempPath
57 | md -Path "$($tempPath)\Bits"
58 |
59 | if (!(test-path "$($imageCachePath)\ubuntu-$($stamp).img")) {
60 | # If we do not have a matching image - delete the old ones and download the new one
61 | Remove-Item "$($imageCachePath)\ubuntu-*.img"
62 | Invoke-WebRequest "$($ubuntuPath)-disk1.img" -UseBasicParsing -OutFile "$($imageCachePath)\ubuntu-$($stamp).img"
63 | }
64 |
65 | # Output meta and user data to files
66 | sc "$($tempPath)\Bits\meta-data" ([byte[]][char[]] "$metadata") -Encoding Byte
67 | sc "$($tempPath)\Bits\user-data" ([byte[]][char[]] "$userdata") -Encoding Byte
68 |
69 | # Convert cloud image to VHDX
70 | & $qemuImgPath convert -f qcow2 "$($imageCachePath)\ubuntu-$($stamp).img" -O vhdx -o subformat=dynamic $vhdx
71 | Resize-VHD -Path $vhdx -SizeBytes 50GB
72 |
73 | # Create meta data ISO image
74 | & $oscdimgPath "$($tempPath)\Bits" $metaDataIso -j2 -lcidata
75 |
76 | # Clean up temp directory
77 | rd -Path $tempPath -Recurse -Force
78 |
79 | # Create new virtual machine and start it
80 | new-vm $VMName -MemoryStartupBytes 2048mb -VHDPath $vhdx -Generation 1 `
81 | -SwitchName $virtualSwitchName -Path $vmPath | Out-Null
82 | set-vm -Name $VMName -ProcessorCount 2
83 | Set-VMDvdDrive -VMName $VMName -Path $metaDataIso
84 | Start-VM $VMName
85 |
86 | # Open up VMConnect
87 | Invoke-Expression "vmconnect.exe localhost `"$VMName`""
88 |
--------------------------------------------------------------------------------
/Ubuntu-VM-Build/readme.md:
--------------------------------------------------------------------------------
1 | Scripts for creating Ubuntu VMs on Hyper-V
2 |
--------------------------------------------------------------------------------
/vmconnect.ps1:
--------------------------------------------------------------------------------
1 | function Start-VMConnect
2 | {
3 |
4 | [CmdletBinding()]
5 | param
6 | (
7 | # CMDLet takes a single parameter of VMNames.
8 | # Can be single or plural, can come from the pipeline.
9 |
10 | [Parameter(Mandatory=$True,ValueFromPipeline=$True)]$VMnames
11 | )
12 |
13 | begin
14 | {
15 | # Path to the binary for FreeRDP
16 |
17 | $FreeRDPPath = "C:\FreeRDP\wfreerdp.exe"
18 | }
19 |
20 | process
21 | {
22 | foreach ($VMname in $VMnames)
23 | {
24 | if ($pscmdlet.ShouldProcess($VMname))
25 | {
26 | # Get the ID of the virtual machine
27 | # If a VM object has been provided - grab the ID directly
28 | # Otherwise, try and get the VM object and get the ID from there
29 |
30 | if ($VMname.GetType().name -eq "VirtualMachine")
31 | {$VMID = $VMname.ID}
32 | else
33 | {$VMID = (get-vm $VMname | Select-Object -first 1).ID}
34 |
35 | # Start FreeRDP
36 | start-process $FreeRDPPath -ArgumentList "/v:localhost:2179 /vmconnect:$($VMID)"
37 | }
38 | }
39 | }
40 |
41 | end {}
42 | }
43 |
--------------------------------------------------------------------------------
|