├── .gitattributes
├── .gitignore
├── DeployImage.psm1
├── DeployWindowsPE.ps1
├── DeployWindowsToGo.ps1
├── DeployWindowsVHDDomain.ps1
├── README.md
├── deployimage.format.ps1xml
├── deployimage.psd1
└── foo.txtg
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 |
--------------------------------------------------------------------------------
/DeployImage.psm1:
--------------------------------------------------------------------------------
1 | function Convert-WIMtoVHD
2 | {
3 | [CmdletBinding()]
4 | Param(
5 | # Size of the VHD file
6 | #
7 | $Size=20GB,
8 | # Obtain the default location of
9 | # Virtual HardDisks in Hyper-V
10 | #
11 | $VHDPath='.\',
12 | # Location of the WindowsImage (WIM)
13 | # File to convert to VHD
14 | #
15 | $Wimfile='C:\Windows7\Install.wim',
16 | # Netbios name of computer and VMName
17 | #
18 | $VM='Contoso-Win7',
19 | # Index of Image in WIM file
20 | #
21 | $Index=1
22 |
23 | )
24 |
25 | If ($Vhdpath -eq '.\' -and (Get-Command Get-vmhost).count -ge 1)
26 | {
27 | $VHDPath=(Get-VMHost).VirtualHardDiskPath
28 | }
29 |
30 | # Define VHD filename
31 | #
32 | $VHD="$VhdPath\$VM.vhd"
33 | $OSDrive=(Get-NextActiveDriveLetter).DriveLetter
34 | If ((Test-Path $VHD) -ne $false -or $OSDrive -eq 'None')
35 | {
36 | Return "Filename $VHD already exists or no available drive letter"
37 | }
38 | Else
39 | {
40 | # Create a new VHD
41 | #
42 | $Result=New-VHD -Path $Vhd -SizeBytes $Size -Dynamic
43 |
44 | # Mount the VHD and identify it's Disk Object
45 | #
46 | $Result=Mount-VHD -Path $vhd
47 | $Disk=Get-Vhd -Path $Vhd | Get-Disk
48 |
49 | # Create a new Partition Structure of style
50 | # MBR, Format and Partition System and OSDrive
51 | #
52 | New-PartitionStructure -Disk $disk -MBR -BootDrive $OSDrive -OSDrive $OsDrive
53 |
54 | # Expand the Windows Image to the OSDrive
55 | #
56 | Expand-WindowsImage -imagepath "$wimfile" -index $Index -ApplyPath "$OSDrive`:\"
57 |
58 | # Send the Boot files to the Disk Structure
59 | #
60 | Send-BootCode -BootDrive $OSDrive -OSDrive $OSDrive
61 | # Dismount the Completed VHD
62 | #
63 | Dismount-VHD $VHD
64 | # Return path of VHD
65 | #
66 | Return $VHD
67 | }
68 | }
69 |
70 | Function Copy-WithProgress
71 | {
72 | [CmdletBinding()]
73 | Param
74 | (
75 | [Parameter(Mandatory=$true,
76 | ValueFromPipelineByPropertyName=$true,
77 | Position=0)]
78 | $Source,
79 | [Parameter(Mandatory=$true,
80 | ValueFromPipelineByPropertyName=$true,
81 | Position=0)]
82 | $Destination
83 |
84 | )
85 | $Source=$Source.tolower()
86 |
87 | $Filelist=get-childitem -path $source -Recurse
88 | $Total=$Filelist.count
89 | $Position=0
90 | foreach ($File in $Filelist)
91 | {
92 | $Filename=$File.Fullname.tolower().replace($Source,'')
93 | $DestinationFile=($Destination+$Filename).replace('\\','\')
94 | Write-Progress -Activity "Copying data from $source to $Destination" -Status "Copying Files" -PercentComplete (($Position/$total)*100)
95 | Copy-Item -path $File.FullName -Destination $DestinationFile
96 | $File.Fullname
97 | $DestinationFile
98 | $Position++
99 | }
100 | }
101 |
102 | <#
103 | .Synopsis
104 | Copies supplied sample scripts from the DeployImage module
105 | .DESCRIPTION
106 | Copy all sample PS1 files from DeployImage to the destination directory
107 | .EXAMPLE
108 | Copies sample scripts to current directory
109 |
110 | Copy-DeployImageSample
111 |
112 | .EXAMPLE
113 | Copies sample scripts to C:\Foo
114 |
115 | Copy-DeployImageSample -Destination C:\Foo
116 |
117 |
118 | #>
119 |
120 | Function Copy-DeployImageSample
121 | {
122 | [CmdletBinding()]
123 | Param
124 | (
125 | [Parameter(Mandatory=$false,
126 | ValueFromPipelineByPropertyName=$true,
127 | Position=0)]
128 | $Destination='.\'
129 |
130 | )
131 | $Modulepath=Split-path -path ((get-module -Name deployimage).path)
132 | get-childitem -Path "$Modulepath\*.ps1" | copy-item -Destination $Destination
133 | }
134 |
135 | <#
136 | .Synopsis
137 | Removes a Drive Letter from an assigned partition
138 | .DESCRIPTION
139 | Removes a Drive Letter from an assigned partition
140 | .EXAMPLE
141 | Remove L: from it's assigned partition, freeing it back to available drive letters
142 |
143 | Remove-DriveLetter -DriveLetter L
144 | #>
145 |
146 | Function Remove-DriveLetter
147 | {
148 | [CmdletBinding()]
149 | Param
150 | (
151 | [Parameter(Mandatory=$false,
152 | ValueFromPipelineByPropertyName=$true,
153 | Position=0)]
154 | [string]$DriveLetter
155 | )
156 |
157 | Get-Volume -Drive $DriveLetter | Get-Partition | Remove-PartitionAccessPath -accesspath "$DriveLetter`:\"
158 |
159 | Do {
160 | $status=(Get-Volume -DriveLetter $DriveLetter -erroraction SilentlyContinue)
161 | }
162 | until ($Status -eq $NULL)
163 | }
164 |
165 | <#
166 | .Synopsis
167 | Builds an array of Drive Letters in use in Windows
168 | .DESCRIPTION
169 | This will return an Array of Letters sorted that are presently in use in the Windows O/S
170 | .EXAMPLE
171 | Get-ActiveDriveLetter
172 | #>
173 |
174 | Function Get-ActiveDriveLetter()
175 | {
176 |
177 | Get-Volume | Where-Object { $_.DriveLetter } | Sort-object DriveLetter | Select-Object -ExpandProperty DriveLetter
178 |
179 | }
180 |
181 | <#
182 | .Synopsis
183 | Tests if a Drive Letter is availale in Windows
184 | .DESCRIPTION
185 | This will return a $TRUE if a Drive Letter is available to use in the Windows O/S
186 | .EXAMPLE
187 | Test-ActiveDriveLetter
188 | #>
189 |
190 | Function Test-ActiveDriveLetter([String]$DriveLetter)
191 | {
192 |
193 | $DriveLetter -in (Get-ActiveDriveLetter)
194 |
195 | }
196 |
197 | <#
198 | .Synopsis
199 | Provides the Next available Drive Letter for use in Windows
200 | .DESCRIPTION
201 | This will the next letter available for use
202 | .EXAMPLE
203 | Get-NextActiveDriveLetter
204 | #>
205 |
206 | Function Get-NextActiveDriveLetter()
207 | {
208 | $Counter=67
209 | do
210 | {
211 | $DriveLetter=[char][byte]$Counter
212 | $Result=Test-ActiveDriveLetter -DriveLetter $DriveLetter
213 | $Counter++
214 | }
215 | until ($Result -eq $False -or $Counter -eq 91)
216 |
217 | If ($Result -eq $True)
218 | {
219 | [pscustomobject]@{'DriveLetter'='None'}
220 | }
221 | else
222 | {
223 | [pscustomobject]@{'DriveLetter'=$DriveLetter}
224 | }
225 | }
226 |
227 | <#
228 | .Synopsis
229 | Identifies if the Operating System is 32bit or 64bit
230 | .DESCRIPTION
231 | If the Operating system is 64bit, it will return the string " (x86)" to append to a "Program Files" path
232 | .EXAMPLE
233 | $Folder="C:\Program Files$(Get-Architecture)"
234 | #>
235 |
236 | function Get-ArchitectureString
237 | {
238 | $Arch=(Get-CimInstance -Classname win32_operatingsystem).OSArchitecture
239 | if ($Arch -eq '64-Bit')
240 | {
241 | Return [string]' (x86)'
242 | }
243 |
244 | }
245 |
246 | <#
247 | .Synopsis
248 | Tests for the existence of the Windows 10 ADK
249 | .DESCRIPTION
250 | This Cmdlet will return a Boolean True if the Windows 10 ADK is installed. It depends upon the Get-ArchitectureString Cmdlet supplied within this module
251 | .EXAMPLE
252 | $AdkInstalled = Test-WindowsADK
253 | .EXAMPLE
254 | If (Test-WindowsADK -eq $TRUE)
255 | {
256 | Write-Output 'Windows 10 ADK is installed'
257 | }
258 |
259 | #>
260 |
261 | function Test-WindowsADK
262 | {
263 | (Test-Path -Path "C:\Program Files$(Get-ArchitectureString)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment")
264 | }
265 |
266 | function Get-AttachedDisk
267 | {
268 | [CmdletBinding()]
269 | Param
270 | (
271 | [Parameter(Mandatory=$false,
272 | ValueFromPipelineByPropertyName=$true,
273 | Position=0)]
274 | [switch]$USB,
275 | [Parameter(Mandatory=$false,
276 | ValueFromPipelineByPropertyName=$true,
277 | Position=2)]
278 | [switch]$GUI
279 | )
280 |
281 | If ($USB)
282 | {
283 | $DiskType='USB'
284 | }
285 | Else
286 | {
287 | $DiskType='SATA','SCSI','RAID'
288 | }
289 |
290 |
291 | if ($GUI -and ((Get-CimInstance -Classname Win32_OperatingSystem).OperatingSystemSKU -ne 1))
292 | {
293 | Get-Disk | Where-Object { $DiskType -match $_.BusType } | Out-GridView -PassThru
294 | }
295 | Else
296 | {
297 | Get-Disk | Where-Object { $DiskType -match $_.BusType }
298 | }
299 | }
300 |
301 | <#
302 | .Synopsis
303 | Erase the Partition structure on a Disk
304 | .DESCRIPTION
305 | When provided with a Disk object from "Get-Disk" it will target and cleanly remove all partitions. It addresses some of the limitations found with the Clear-Disk Cmdlet.
306 | .EXAMPLE
307 | Erase partition structure on Disk Number 1
308 |
309 | $Disk=Get-Disk -Number 1
310 | Clear-DiskStructure -Disk $disk
311 | .EXAMPLE
312 | Erase partition structure on all USB attached disks
313 |
314 | $DiskList=Get-Disk | Where { $_.BusType -eq 'USB'}
315 | Foreach ( $Disk in $Disklist )
316 | {
317 | Clear-DiskStructure -Disk $Disk
318 | }
319 |
320 | #>
321 |
322 | function Clear-DiskStructure
323 | {
324 | [CmdletBinding()]
325 | Param
326 | (
327 | [Parameter(Mandatory=$true,
328 | ValueFromPipelineByPropertyName=$true,
329 | Position=0)]
330 | $Disk
331 |
332 | )
333 | Get-Disk -Number ($Disk.number) | Get-Partition | Remove-partition -confirm:$false -erroraction SilentlyContinue
334 | Clear-Disk -Number $Disk.Number -RemoveData -RemoveOEM -confirm:$false -ErrorAction SilentlyContinue
335 | }
336 |
337 | <#
338 | .Synopsis
339 | Create a Partition structure, whether UEFI or MBR
340 | .DESCRIPTION
341 | Creates a Partition structure when provided with a target disk from the GET-Disk Cmdlet including formatting and assigning drive letters.
342 | .EXAMPLE
343 | Create a UEFI Partition structure on Disk 0, assign Drive Z: to the System Drive and Drive Y: to the OSDrive
344 |
345 | $Disk=Get-Disk -number 0
346 | New-PhysicalPartitionStructure -Disk $Disk -BootDrive Z -OSDrive Y
347 |
348 | #>
349 |
350 | function New-PartitionStructure
351 | {
352 | [CmdletBinding()]
353 | Param
354 | (
355 | [Parameter(Mandatory=$true,
356 | ValueFromPipelineByPropertyName=$true,
357 | Position=0)]
358 | $Disk,
359 | [Parameter(Mandatory=$false,
360 | ValueFromPipelineByPropertyName=$true,
361 | Position=1)]
362 | [switch]$MBR,
363 | [Parameter(Mandatory=$false,
364 | ValueFromPipelineByPropertyName=$true,
365 | Position=2)]
366 | [switch]$USB,
367 | [Parameter(Mandatory=$false,
368 | ValueFromPipelineByPropertyName=$true,
369 | Position=3)]
370 | [string]$BootDrive,
371 | [Parameter(Mandatory=$false,
372 | ValueFromPipelineByPropertyName=$true,
373 | Position=4)]
374 | [string]$OSDrive
375 |
376 | )
377 |
378 | Clear-DiskStructure $Disk
379 |
380 | if ($MBR)
381 | {
382 | $Result=Initialize-Disk -Number $Disk.Number -PartitionStyle MBR -ErrorAction SilentlyContinue
383 |
384 | if ($USB)
385 | {
386 | $Partition=New-Partition -DiskNumber $Disk.Number -UseMaximumSize -IsActive
387 | $Result=Format-Volume -Partition $Partition -FileSystem FAT32 -NewFileSystemLabel 'Windows'
388 | $Partition | Add-PartitionAccessPath -AccessPath "$($OSDrive):\"
389 |
390 |
391 | }
392 | else
393 | {
394 | $Partition=New-Partition -DiskNumber $Disk.Number -UseMaximumSize -IsActive
395 | $Result=Format-Volume -Partition $Partition -FileSystem NTFS -NewFileSystemLabel 'Windows'
396 | $Partition | Add-PartitionAccessPath -AccessPath "$($OSDrive):\"
397 | }
398 |
399 | }
400 | Else
401 | {
402 | $Result=Initialize-Disk -Number $Disk.Number -PartitionStyle GPT
403 |
404 | $Partition=New-Partition -DiskNumber $Disk.Number -Size 128MB ; # Create Microsoft Basic Partition
405 | $Result=Format-Volume -Partition $Partition -FileSystem Fat32 -NewFileSystemLabel 'MSR'
406 | $Result=Set-Partition -DiskNumber $Disk.Number -PartitionNumber $Partition.PartitionNumber -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}'
407 |
408 | $Partition=New-Partition -DiskNumber $Disk.Number -Size 300MB ; # Create Microsoft Basic Partition and Set System as bootable
409 | $Result=Format-Volume -Partition $Partition -FileSystem Fat32 -NewFileSystemLabel 'Boot'
410 | $Partition | Add-PartitionAccessPath -AccessPath "$($Bootdrive):\"
411 |
412 | $Result=Set-Partition -DiskNumber $Disk.Number -PartitionNumber $Partition.PartitionNumber
413 |
414 | $Partition=New-Partition -DiskNumber $Disk.Number -UseMaximumSize ; # Take remaining Disk space for Operating System
415 | $Result=Format-Volume -Partition $Partition -FileSystem NTFS -NewFileSystemLabel 'Windows'
416 | $Partition | Add-PartitionAccessPath -AccessPath "$($OSDrive):\"
417 |
418 | }
419 |
420 | }
421 |
422 |
423 | <#
424 | .Synopsis
425 | Set San Policy for a Windows To Go key
426 | .DESCRIPTION
427 | Creates and injects the necessary Disk policy which protects Windows to Go from Internal Drives. Requires the drive letter of the target OSDrive.
428 | .EXAMPLE
429 | Set Windows To Go key on Drive L with the proper San Policy
430 |
431 | Send-SanPolicy -OSDrive L
432 | #>
433 |
434 | Function Send-SanPolicy
435 | {
436 | [CmdletBinding()]
437 | Param
438 | (
439 | [Parameter(Mandatory=$true,
440 | ValueFromPipelineByPropertyName=$true,
441 | Position=0)]
442 | $OSDrive
443 | )
444 |
445 | $SanPolicyXML=@"
446 |
447 |
448 |
449 |
458 | 4
459 |
460 |
469 | 4
470 |
471 |
472 |
473 | "@
474 |
475 | Add-content -path "$OSDrive`:\san-policy.xml" -Value $SanpolicyXML
476 |
477 | Use-WindowsUnattend -UnattendPath "$OSDrive`:\san-policy.xml" -path "$OSdrive`:\" | Out-Null
478 | }
479 |
480 | <#
481 | .Synopsis
482 | Creates an Unattend.XML file for injection into an O/S
483 | .DESCRIPTION
484 | This Cmdlet will create an Unattend.XML file with suitable content. Depending upon the provided parameters it can inject the needed content or credentials for a Domain Join or be left to join a Workgroup Configuration.
485 | .EXAMPLE
486 | Create Unattend file for a computer named TestPC with all defaults for Password
487 |
488 | New-UnattendXMLContent -computername TestPC
489 | .EXAMPLE
490 |
491 |
492 | #>
493 | function New-UnattendXMLContent
494 | {
495 | [CmdletBinding()]
496 | Param
497 | (
498 | [Parameter(Mandatory=$true,
499 | ValueFromPipelineByPropertyName=$true,
500 | Position=0)]
501 | [string]$Computername,
502 | [Parameter(Mandatory=$false,
503 | ValueFromPipelineByPropertyName=$true,
504 | Position=1)]
505 | [string]$Timezone='Eastern Standard Time',
506 | [Parameter(Mandatory=$false,
507 | ValueFromPipelineByPropertyName=$true,
508 | Position=2)]
509 | [string]$Owner='Nano Owner',
510 | [Parameter(Mandatory=$false,
511 | ValueFromPipelineByPropertyName=$true,
512 | Position=3)]
513 | [string]$Organization='Nano Organization',
514 | [Parameter(Mandatory=$false,
515 | ValueFromPipelineByPropertyName=$true,
516 | Position=4)]
517 | [string]$AdminPassword='P@ssw0rd',
518 | [Parameter(Mandatory=$false,
519 | ValueFromPipelineByPropertyName=$true,
520 | Position=5,
521 | ParameterSetName='DomainOnline')]
522 | [Switch]$Online,
523 | [Parameter(Mandatory=$false,
524 | ValueFromPipelineByPropertyName=$true,
525 | Position=6,
526 | ParameterSetName='DomainOnline')]
527 | [string]$DomainName,
528 | [Parameter(Mandatory=$false,
529 | ValueFromPipelineByPropertyName=$true,
530 | Position=7,
531 | ParameterSetName='DomainOnline')]
532 | [string]$DomainAccount,
533 | [Parameter(Mandatory=$false,
534 | ValueFromPipelineByPropertyName=$true,
535 | Position=8,
536 | ParameterSetName='DomainOnline')]
537 | [string]$DomainPassword,
538 | [Parameter(Mandatory=$false,
539 | ValueFromPipelineByPropertyName=$true,
540 | Position=9,
541 | ParameterSetName='DomainOnline')]
542 | [string]$DomainOU,
543 | [Parameter(Mandatory=$false,
544 | ValueFromPipelineByPropertyName=$true,
545 | Position=10)]
546 | [string]$OfflineBlob,
547 | [Parameter(Mandatory=$false,
548 | ValueFromPipelineByPropertyName=$true,
549 | Position=11)]
550 | [switch]$SkipOOBE
551 |
552 | )
553 |
554 | $UnattendXML=@"
555 |
556 |
557 |
558 |
559 | $Computername
560 | $Organization
561 | $Owner
562 | $Timezone
563 |
564 | "@
565 |
566 | If($JoinDomain -eq 'Online')
567 | {
568 | $UnattendXML=$UnattendXML+@"
569 |
570 |
571 |
572 |
573 | $DomainName
574 | $Domainpassword
575 | $Domainaccount
576 |
577 | $DomainName
578 | $DomainOU
579 | False
580 |
581 |
582 | "@
583 | }
584 |
585 | if($Network)
586 | {
587 | $UnattendXML=$UnattendXML+@"
588 |
589 |
590 |
591 | false
592 |
593 | Local Area Connection
594 |
595 | $IPv4/$Mask
596 |
597 |
598 |
599 | 0
600 | 0.0.0.0/0
601 | 20
602 | $Gateway
603 |
604 |
605 |
606 |
607 | "@
608 | }
609 |
610 | $UnattendXML=$UnattendXML+@"
611 |
612 |
613 |
614 |
615 |
616 |
617 | $Adminpassword
618 | true
619 |
620 |
621 |
622 |
623 | $Adminpassword
624 | true
625 |
626 | administrator
627 | 1
628 | true
629 |
630 | $Organization
631 | $Owner
632 | "@
633 |
634 | If ($SkipOOBE)
635 | {
636 | $UnattendXML=$UnattendXML+@"
637 |
638 | true
639 | true
640 |
641 |
642 | "@
643 | }
644 |
645 | $UnattendXML=$UnattendXML+@"
646 |
647 |
648 | "@
649 |
650 | If ($OfflineBlob)
651 | {
652 | $UnattendXML=$UnattendXML+@"
653 |
654 |
655 |
656 |
657 |
658 | $OfflineBlob
659 |
660 |
661 |
662 |
663 | "@
664 | }
665 |
666 |
667 | $UnattendXML=$UnattendXML+@"
668 |
669 |
670 |
671 | "@
672 |
673 |
674 | Return $UnattendXML
675 |
676 | }
677 |
678 | Function Send-UnattendXML
679 | {
680 | [CmdletBinding()]
681 | Param
682 | (
683 | [Parameter(Mandatory=$true,
684 | ValueFromPipelineByPropertyName=$true,
685 | Position=0)]
686 | [string]$OSDrive,
687 | [Parameter(Mandatory=$true,
688 | ValueFromPipelineByPropertyName=$true,
689 | Position=1)]
690 | [string]$UnattendData
691 | )
692 | $Filename="$((Get-random).ToString()).xml"
693 |
694 | Remove-item -Path $Filename -force -ErrorAction SilentlyContinue
695 | New-Item -ItemType File -Path $Filename
696 | Add-Content -Path $Filename -Value $UnattendData
697 | Copy-Item -Path $filename -Destination "$OSDrive`:\Windows\System32\Sysprep\unattend.xml"
698 | Remove-item -Path $Filename -force -ErrorAction SilentlyContinue
699 |
700 | }
701 |
702 | function Send-BootCode
703 | {
704 | [CmdletBinding()]
705 | Param
706 | (
707 | [Parameter(Mandatory=$false,
708 | ValueFromPipelineByPropertyName=$true,
709 | Position=0)]
710 | [string]$BootDrive,
711 | [Parameter(Mandatory=$true,
712 | ValueFromPipelineByPropertyName=$true,
713 | Position=1)]
714 | [string]$OSDrive,
715 | [Parameter(Mandatory=$false,
716 | ValueFromPipelineByPropertyName=$true,
717 | Position=2)]
718 | [switch]$USB
719 |
720 | )
721 | $oldpref=$ErrorActionPreference
722 | $ErrorActionPreference='SilentlyContinue'
723 |
724 | if ($USB)
725 | {
726 | & "$($env:windir)\system32\bootsect.exe" /nt60 "$OSDrive`:" > NULL
727 | }
728 | else
729 | {
730 | & "$($env:windir)\system32\bcdboot" "$OSDrive`:\Windows" /s "$BootDrive`:" /f ALL > NULL
731 | }
732 | $ErrorActionPreference=$oldpref
733 | }
734 |
735 |
736 | function New-WindowsPEWim
737 | {
738 | [CmdletBinding()]
739 | Param
740 | (
741 | [Parameter(Mandatory=$false,
742 | ValueFromPipelineByPropertyName=$true)]
743 | $WinPETemp='C:\TempPE',
744 | [Parameter(Mandatory=$false,
745 | ValueFromPipelineByPropertyName=$true)]
746 | $Destination='C:\PeWim'
747 |
748 | )
749 |
750 | Begin
751 | {
752 | }
753 |
754 | Process
755 | {
756 | $Env:WinPERoot="C`:\Program Files$(Get-ArchitectureString)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment"
757 |
758 | $WinADK="$($Env:WinPERoot)\amd64"
759 |
760 | Remove-item -Path $WinPETemp -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
761 | New-Item -ItemType Directory -Path $WinPETemp -Force | Out-Null
762 | Copy-Item -Path "$WinAdk\Media" -Destination $WinPETemp -Recurse -Force | Out-Null
763 | New-Item -ItemType Directory -Path "$WinPETemp\Media\Sources" -Force | Out-Null
764 | Copy-Item -path "$WinAdk\en-us\winpe.wim" -Destination "$WinPETemp\Media\Sources\boot.wim" | Out-Null
765 | New-Item -ItemType Directory -Path "$WinPETemp\Mount" -Force | Out-Null
766 |
767 | Mount-WindowsImage -ImagePath "$WinPETemp\Media\Sources\boot.wim" -Index 1 -path "$WinPETemp\Mount" | Out-Null
768 |
769 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-WMI.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
770 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-WMI_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
771 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-NetFx.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
772 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-NetFx_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
773 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-Scripting.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
774 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-Scripting_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
775 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-PowerShell.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
776 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-PowerShell_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
777 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-DismCmdlets.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
778 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-DismCmdlets_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
779 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-EnhancedStorage.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
780 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-EnhancedStorage_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
781 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\WinPE-StorageWMI.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
782 | Add-WindowsPackage -PackagePath "$($WinAdk)\Winpe_OCS\en-us\WinPE-StorageWMI_en-us.cab" -Path "$WinPeTemp\Mount" -IgnoreCheck | Out-Null
783 |
784 | # Custom PowerShell Script to launch after Wpeinit.exe
785 | # This is hardcoded presently to automatically
786 | # Start the DeployImage module
787 | # from the WinPE media for Easy Server Deployment
788 | $PowerShellScript=@'
789 | Set-ExecutionPolicy -executionpolicy Bypass
790 | $USBDisk=(Get-Disk | Where-Object { $_.BusType -eq 'USB' -and '$_.IsActive' })
791 | $DriveLetter=($USBDisk | Get-Partition).DriveLetter
792 | Set-Location ($DriveLetter+':\DeployImage\')
793 | Import-Module ($DriveLetter+':\DeployImage\DeployImage.Psd1')
794 | '@
795 |
796 | # Carriage Return (Ascii13) and Linefeed (Ascii10)
797 | # the characters at the end of each line in a Here String
798 | $CRLF=[char][byte]13+[char][byte]10
799 |
800 | $PowerShellCommand=$PowerShellScript.replace($CRLF,';')
801 |
802 | $PowerShellStart='powershell.exe -executionpolicy bypass -noexit -command "'+$PowerShellCommand+'"'
803 | Add-Content -Path "$WinPEtemp\Mount\Windows\System32\Startnet.cmd" -Value $PowerShellStart
804 |
805 | Dismount-WindowsImage -path "$WinPETemp\Mount" -Save | Out-Null
806 |
807 | New-Item -Path $Destination -ItemType Directory -Force | Out-Null
808 | Copy-Item -path "$WinPETemp\Media\Sources\boot.wim" -destination "$Destination\" | Out-Null
809 | Remove-Item -Path "$Destination\Custom.wim" -erroraction SilentlyContinue | Out-Null
810 | Rename-Item -Path "$Destination\Boot.wim" -NewName 'Custom.wim' | Out-Null
811 | Remove-item -Path $WinPETemp -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
812 | Return "$Destination\Custom.wim"
813 |
814 | }
815 | End
816 | {
817 | }
818 | }
819 |
--------------------------------------------------------------------------------
/DeployWindowsPE.ps1:
--------------------------------------------------------------------------------
1 | $Wimfile='C:\Pewim\custom.wim'
2 | $OSDrive='L'
3 | $WinPEDrive='C'
4 | $Disk
5 | $DriverPath='C:\Dell'
6 | $WinPETemp='C:\TempPE'
7 |
8 | $Disk=Get-AttachedDisk -USB -GUI
9 | $Env:WinPERoot="$($WinPEDrive)`:\Program Files$(Get-ArchitectureString)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment"
10 | $WinADK="$($Env:WinPERoot)\amd64"
11 |
12 | Remove-item -Path $WinPETemp -Recurse -Force
13 | New-Item -ItemType Directory -Path $WinPETemp -Force
14 | Copy-Item -Path "$WinAdk\Media" -Destination $WinPETemp -Recurse -Force
15 | New-Item -ItemType Directory -Path "$WinPETemp\Media\Sources" -Force
16 | Copy-Item -path "$WinAdk\en-us\winpe.wim" -Destination "$WinPETemp\Media\Sources\boot.wim"
17 |
18 | if ($Wimfile -ne $NULL)
19 | {
20 | Copy-Item -Path $Wimfile -Destination "$WinPETemp\Media\Sources\boot.wim"
21 | }
22 |
23 | New-PartitionStructure -Disk $disk -OSDrive $OSDrive -USB -MBR
24 | $WinPEKey=$OsDrive+':'
25 |
26 | Copy-Item -Path "$WinPETemp\Media\*" -destination "$WinPeKey\" -Recurse
27 |
28 | Send-BootCode -OSDrive $OSDrive -USB
29 |
30 | Remove-DriveLetter -DriveLetter $OSDrive
31 |
--------------------------------------------------------------------------------
/DeployWindowsToGo.ps1:
--------------------------------------------------------------------------------
1 | $Wimfile='C:\Sources\Install.wim'
2 | $BootDrive='Y'
3 | $OSDrive='Z'
4 |
5 | $Disk=Get-AttachedDisk -GUI
6 | $DriverPath='C:\Drivers'
7 |
8 | New-PartitionStructure -Disk $disk -BootDrive $BootDrive -OSDrive $OsDrive
9 | Expand-WindowsImage –imagepath "$wimfile" –index 1 –ApplyPath "$OSDrive`:\"
10 |
11 | $Unattendfile='C:\Foo\Unattend.xml'
12 | $Content=New-UnattendXMLContent -computername WTGKey -TimeZone 'Eastern Standard Time'
13 | Add-Content -path $Unattendfile -value $Content
14 | Copy-Item $UnattendFile -destination "$OSDrive`:\Windows\System32\Sysprep"
15 | Send-BootCode -BootDrive $BootDrive -OSDrive $OSDrive
16 | Send-SanPolicy -OSDrive $OSDrive
17 |
18 | If ($DriverPath -ne $NULL)
19 | {
20 | Add-WindowsDriver -Driver $DriverPath -Recurse -Path "$OSDrive`:\" -ErrorAction SilentlyContinue
21 | }
22 |
--------------------------------------------------------------------------------
/DeployWindowsVHDDomain.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | $SystemDrive='L',
3 | # Drive Letter being assigned to Windows Drive
4 | #
5 | $OSDrive='L',
6 | # Netbios name of computer and VMName
7 | #
8 | $Computername='Contoso-Win7'
9 | )
10 | $VHD=Convert-WimtoVhd -Wimfile C:\Windows7\install.wim -vm $Computername
11 |
12 | $Mount=Mount-vhd $VHD
13 | Add-PartitionAccessPath -DiskId
14 |
15 | # Clear out the old Unattend.xml file if it exists
16 | #
17 | Remove-Item -Path Unattend.xml -Force -ErrorAction SilentlyContinue
18 |
19 | # Create an Unattend.XML to define the Computername, owner,
20 | # Timezone and default Admin Password
21 | #
22 | $XMLContent=New-UnattendXMLContent -Computername $Computername -Timezone 'Eastern Standard Time' -Owner 'Contoso' -Organization 'Contoso' -AdminPassword 'P@ssw0rd' -Online -DomainName Contoso -DomainAccount 'Administrator' -DomainPassword 'P@ssw0rd'
23 |
24 | # Create the Unattend.xml file
25 | #
26 | New-Item -ItemType File -Name Unattend.xml -Force | Out-Null
27 | Add-content Unattend.xml -Value $XMLContent
28 |
29 | # Inject the Unattend.xml file into the VHD image
30 | #
31 | Copy .\Unattend.xml "$OSdrive`:\Windows\system32\sysprep"
32 |
33 | # Build the post Unattend.xml - Pre login script
34 | # This will define the static IP address and Perform an
35 | # Offline Domain Join from the provide domainjoin.djoin file
36 | #
37 | $SetupCompleteCMD=@"
38 | "@
39 |
40 | # Remove the old SetupComplete.CMD if it exists
41 | #
42 | Remove-Item -Path SetupComplete.cmd -Force -ErrorAction SilentlyContinue
43 |
44 | # Create the new one
45 | #
46 | New-Item -ItemType File -Name SetupComplete.cmd -Force | Out-Null
47 | Add-content SetupComplete.cmd -Value $SetupCompleteCMD
48 |
49 | # Inject into the disk image
50 | #
51 | Copy .\SetupComplete.cmd "$OSdrive`:\Windows\setup\scripts"
52 |
53 | # Remove Drive Letter from Assigned Disk and place
54 | # back into pool.
55 | #
56 | Remove-DriveLetter -DriveLetter $OSDrive
57 |
58 | # Disconnect VHD
59 | #
60 | Dismount-VHD -Path $vhd
61 |
62 | # From this point forward you can manually create a
63 | # Virtual Machine in Hyper-V or use this VHD file for
64 | # booting on a Physical Disk
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DeployImage
2 | The goal of this PowerShell Module initially is to supply a better and easier way to image the new Nano Server from Microsoft. However since the processes are for the most part identical, I am going to extend this to Windows to Go deployments as well as standard servers (whether physical or virtual)
3 |
4 | It will have to ability to create a Workgroup or Domain based Unattend.xml file to be injected directly into the destination environment.
5 |
6 | In addition (as it grows and improves) it will be able to inject features directly into the relevant operating systems.
7 |
8 | Another target of this Module is to allow easy Deployment of a Windows PE key that is PowerShell enabled.
9 |
10 | This module can be found on the PowerShell Gallery under 'DeployImage' and also on Github under https://github.com/energizedtech/DeployImage
11 |
12 | It introduces 13 new Cmdlets to try and simplify Deploying Nanoserver and Wim files to VHD, USB and Physical Disk environments. (Nano Server being the Dominant reason)
13 |
14 | Clear-DiskStructure
15 |
16 | This Cmdlets task is to Enhance the Clear-Disk Cmdlet and simplify it as I have encountered situations in which you need to remove Partitions first before Clear-Disk successfully cleans the Target Disk
17 |
18 | Copy-WithProgress
19 |
20 | This Cmdlet is still being tested and is meant as a "Copy-Item" with Progress to show SOME indication of where a large folder copy is at
21 |
22 | Get-ArchitectureString
23 |
24 | This Cmdlet returns the string " (x86)" if you are on 64bi Windows. It is meant as a way to inject into the 32 Program Files pathname
25 |
26 | Get-ActiveDriveLetter
27 |
28 | This Cmdlet pulls up an array of Drive Letters presently in use
29 |
30 | Test-ActiveDriveLetter
31 |
32 | It will provide a Boolean $True or $False if a Drive Letter is in use when provided
33 |
34 | Get-NextActiveDriveLetter
35 |
36 | This Cmdlet will identify the next available drive letter for mapping or other purposes
37 |
38 | Get-AttachedDisk
39 |
40 | Get-AttachedDisk is a Wrapper on the Get-Disk Cmdlet which targets either Internal or USB. It also provides a GUI parameter to launch Out-Gridview to select disks
41 |
42 | New-PartitionStructure
43 |
44 | This Cmdlet creates one of three stock basic Parition structures, either MBR for USB, MBR for Filesystem or GPT for UEFI and Formats targeted drives
45 |
46 | New-UnattendXMLContent
47 |
48 | This is a Parameterized Cmdlet to provide an Unattend.XML file content. Domain Join is an option and will populate the additional needed content.
49 |
50 | New-WindowsPEWim
51 |
52 | This builds a WindowsPE Wim file pre-populated with the necessary OCS for Windows PowerShell, DISM and Storage Cmdlets. It also autolaunches PowerShell with the Set-ExecutionPolicy to Bypass
53 |
54 | Remove-DriveLetter
55 |
56 | This simply Removes and verifies removal of a Drive Letter from the Drive system
57 |
58 | Send-BootCode
59 |
60 | Makes a Disk bootable
61 |
62 | Send-SanPolicy
63 |
64 | This is meant for Windows to Go. It just builds and applies a targeted disk with the SanPolicy for Windows to Go
65 |
66 | Send-UnattendXML
67 |
68 | Deploys an Unattend file to a target Operating System
69 |
70 | Test-WindowsADK
71 |
72 | Test for the Presence of the Windows 10 ADK
73 |
74 | There are also a sample script provided
75 |
76 | DeployWindowsPE.ps1
77 |
78 | Prompts for an available USB key, Prepares it as Bootable WinPE with PowerShell
79 |
80 |
--------------------------------------------------------------------------------
/deployimage.format.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/deployimage.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'deployimage'
3 | #
4 | # Generated by: Sean Kearney
5 | #
6 | # Generated on: 8/14/2019
7 | #
8 |
9 | @{
10 |
11 | # Script module or binary module file associated with this manifest.
12 | # RootModule = ''
13 |
14 | # Version number of this module.
15 | ModuleVersion = '2.1'
16 |
17 | # Supported PSEditions
18 | # CompatiblePSEditions = @()
19 |
20 | # ID used to uniquely identify this module
21 | GUID = '4de6b7a8-bb7d-4209-bb48-d5327ba26f0f'
22 |
23 | # Author of this module
24 | Author = 'Sean Kearney'
25 |
26 | # Company or vendor of this module
27 | CompanyName = 'Energized About Technology'
28 |
29 | # Copyright statement for this module
30 | Copyright = '(c) 2015 Sean Kearney. All rights reserved.'
31 |
32 | # Description of the functionality provided by this module
33 | Description = 'A module to help simplify the of WindowsPE, Windows to Go and other WIM files to both physical and VHD'
34 |
35 | # Minimum version of the Windows PowerShell engine required by this module
36 | PowerShellVersion = '2.0'
37 |
38 | # Name of the Windows PowerShell host required by this module
39 | # PowerShellHostName = ''
40 |
41 | # Minimum version of the Windows PowerShell host required by this module
42 | # PowerShellHostVersion = ''
43 |
44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
45 | # DotNetFrameworkVersion = ''
46 |
47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
48 | # CLRVersion = ''
49 |
50 | # Processor architecture (None, X86, Amd64) required by this module
51 | # ProcessorArchitecture = ''
52 |
53 | # Modules that must be imported into the global environment prior to importing this module
54 | # RequiredModules = @()
55 |
56 | # Assemblies that must be loaded prior to importing this module
57 | # RequiredAssemblies = @()
58 |
59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
60 | # ScriptsToProcess = @()
61 |
62 | # Type files (.ps1xml) to be loaded when importing this module
63 | # TypesToProcess = @()
64 |
65 | # Format files (.ps1xml) to be loaded when importing this module
66 | # FormatsToProcess = @()
67 |
68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
69 | NestedModules = @('DeployImage.PSM1')
70 |
71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
72 | FunctionsToExport = ('Clear-DiskStructure,Convert-WIMtoVHD,Copy-DeployImageSample,Copy-WithProgress,Get-ActiveDriveLetter,Get-ArchitectureString,Get-AttachedDisk,Get-NextActiveDriveLetter,New-PartitionStructure,New-UnattendXMLContent,New-WindowsPEWim,Remove-DriveLetter,Send-BootCode,Send-SanPolicy,Send-UnattendXML,Test-ActiveDriveLetter,Test-WindowsADK')
73 |
74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
75 | CmdletsToExport = '*'
76 |
77 | # Variables to export from this module
78 | VariablesToExport = '*'
79 |
80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
81 | AliasesToExport = '*'
82 |
83 | # DSC resources to export from this module
84 | # DscResourcesToExport = @()
85 |
86 | # List of all modules packaged with this module
87 | # ModuleList = @()
88 |
89 | # List of all files packaged with this module
90 | # FileList = @()
91 |
92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
93 | PrivateData = @{
94 |
95 | PSData = @{
96 |
97 | # Tags applied to this module. These help with module discovery in online galleries.
98 | # Tags = @()
99 |
100 | # A URL to the license for this module.
101 | LicenseUri = 'http://opensource.org/licenses/MS-PL'
102 |
103 | # A URL to the main website for this project.
104 | ProjectUri = 'https://github.com/energizedtech/DeployImage'
105 |
106 | # A URL to an icon representing this module.
107 | # IconUri = ''
108 |
109 | # ReleaseNotes of this module
110 | ReleaseNotes = 'Copy-DeployImageSample Cmdlet added to easily provide sample scripts. New-NanoServerWim Cmdlet supports customization of all built in roles, New-WindowsPEwim builds PowerShell 5 PE with autolaunch to PowerShell. Sample scripts to deploy NanoServer, NanoServerMedia and building WindowsPE supplied. Some help added.'
111 |
112 | # Prerelease string of this module
113 | # Prerelease = ''
114 |
115 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save
116 | # RequireLicenseAcceptance = $false
117 |
118 | # External dependent modules of this module
119 | # ExternalModuleDependencies = @()
120 |
121 | } # End of PSData hashtable
122 |
123 | } # End of PrivateData hashtable
124 |
125 | # HelpInfo URI of this module
126 | # HelpInfoURI = ''
127 |
128 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
129 | # DefaultCommandPrefix = ''
130 |
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/foo.txtg:
--------------------------------------------------------------------------------
1 | hello
--------------------------------------------------------------------------------