-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathPE_Helper.ps1
1713 lines (1679 loc) · 77.4 KB
/
PE_Helper.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#requires -version 5.0
# ....
# .'^""""""^.
# '^`'. '^"""""""^.
# .^"""""`' .^"""""""^. ---------------------------------------------------------
# .^""""""` ^"""""""` | DISMTools 0.6 |
# ."""""""^. `""""""""' `,` | The connected place for Windows system administration |
# '`""""""`. """""""""^ `,,," ---------------------------------------------------------
# '^"""""`. ^""""""""""'. .`,,,,,^ | Preinstallation Environment (PE) helper |
# .^"""""`. ."""""""",,,,,,,,,,,,,,,. ---------------------------------------------------------
# .^"""""^. .`",,"""",,,,,,,,,,,,,,,,' | (C) 2024 CodingWonders Software |
# .^"""""^. '`^^"",:,,,,,,,,,,,,,,,,,". ---------------------------------------------------------
# .^"""""^.`+]>,^^"",,:,,,,,,,,,,,,,`.
# .^""";_]]]?)}:^^""",,,`'````'..
# .;-]]]?(xxxx}:^^^^'
# `+]]]?(xxxxxxxr},'
# .`:+]?)xxxxxxxxxxxr<.
# .`^^^^:(xxxxxxxxxxxxxxr>.
# .`^^^^^^^^I(xxxxxxxxxxxxxxr<.
# .`^^^^^^^^^^^^I(xxxxxxxxxxxxxxr<.
# .`^^^^^^^^^^^^^^^'`[xxxxxxxxxxxxxxr<.
# .`^^^^^^^^^^^^^^^' `}xxxxxxxxxxxxxxr<.
# `^^":ll:"^^^^^^^' `}xxxxxxxxxxxxxxr,
# '^^^I-??]l^^^^^' `[xxxxxxxxxxxxxx. This script is provided AS IS, without any warranty. It shouldn't
# '^^^,<??~,^^^' `{xxxxxxxxxxxx. do any damage to your computer, but you still need to be careful over
# `^^^^^^^^^' `{xxxxxxxxr, what you do with it.
# .'`^^^`' `i1jrt[:.
using namespace System.Collections.Generic
[CmdletBinding(DefaultParameterSetName='Default')]
param (
[Parameter(Mandatory = $true, Position = 0)] [ValidateSet('StartPEGen', 'StartApply', 'StartDevelopment', 'Help')] [string]$cmd,
[Parameter(ParameterSetName = 'StartPEGen', Mandatory = $true, Position = 1)] [string]$arch,
[Parameter(ParameterSetName = 'StartPEGen', Mandatory = $true, Position = 2)] [string]$imgFile,
[Parameter(ParameterSetName = 'StartPEGen', Mandatory = $true, Position = 3)] [string]$isoPath,
[Parameter(ParameterSetName = 'StartPEGen', Position = 4)] [string]$unattendFile,
[Parameter(ParameterSetName = 'StartDevelopment', Mandatory = $true, Position = 1)] [string]$testArch,
[Parameter(ParameterSetName = 'StartDevelopment', Mandatory = $true, Position = 2)] [string]$targetPath
)
enum PE_Arch {
x86 = 0
amd64 = 1
arm = 2
arm64 = 3
}
class TargetImage {
[int]$index
[string]$wimPath
TargetImage() { $this.Init(@{} )}
# Create constructor
TargetImage([int]$index, [string]$wimPath) {
$this.index = $index
$this.wimPath = $wimPath
}
}
if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -eq $false)
{
Write-Host "You need to run this script as an administrator"
exit 1
}
function Start-PEGeneration
{
<#
.SYNOPSIS
Generates a Preinstallation Environment (PE) that contains the Windows image specified in the GUI or via the command line
#>
$mountDirectory = ""
$architecture = [PE_Arch]::($arch)
$version = "0.6"
Write-Host "DISMTools $version - Preinstallation Environment Helper"
Write-Host "(c) 2024. CodingWonders Software"
Write-Host "-----------------------------------------------------------"
# Start PE generation
Write-Host "Starting PE generation..."
# Detect if the Windows ADK is present
try
{
if ((Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\WIMMount' -Name 'AdkInstallation') -eq 1)
{
# An ADK may be installed, but it may not be Windows 10 ADK
$progFiles = ""
$peToolsPath = ""
if ([Environment]::Is64BitOperatingSystem)
{
$progFiles = "$([IO.Path]::GetPathRoot([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)))Program Files (x86)"
}
else
{
$progFiles = "$([IO.Path]::GetPathRoot([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)))Program Files"
}
if (Test-Path "$progFiles\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment")
{
$peToolsPath = "$progFiles\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment"
Write-Host "Creating working directory and copying Preinstallation Environment (PE) files..."
if ((Copy-PEFiles -peToolsPath "$progFiles\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment" -architecture $architecture -targetDir "$((Get-Location).Path)\ISOTEMP") -eq $false)
{
Write-Host "Preinstallation Environment creation has failed in the PE file copy phase."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Creating temporary mount directory..."
try
{
$mountDirectory = "$env:TEMP\DISMTools_PE_Scratch_$((Get-Date).ToString("MM-dd-yyyy_HH-mm-ss"))_$(Get-Random -Maximum 10000)"
New-Item "$mountDirectory" -ItemType Directory | Out-Null
}
catch
{
Write-Host "Could not create temporary mount directory. Using default folder..."
$mountDirectory = "$((Get-Location).Path)\ISOTEMP\mount"
}
Write-Host "Mounting Windows image. Please wait..."
if ((Start-DismCommand -Verb Mount -ImagePath "$((Get-Location).Path)\ISOTEMP\media\sources\boot.wim" -ImageIndex 1 -MountPath "$mountDirectory") -eq $false)
{
Write-Host "Preinstallation Environment creation has failed in the PE image mount phase."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Copying Windows PE optional components. Please wait..."
if ((Copy-PEComponents -peToolsPath "$progFiles\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment" -architecture $architecture -targetDir "$((Get-Location).Path)\ISOTEMP") -eq $false)
{
Write-Host "Preinstallation Environment creation has failed in the PE optional component copy phase."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Adding OS packages..."
$pkgs = [List[string]]::new()
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\WinPE-NetFx.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\en-US\WinPE-NetFx_en-us.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\WinPE-WMI.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\en-US\WinPE-WMI_en-us.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\WinPE-PowerShell.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\en-US\WinPE-PowerShell_en-us.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\WinPE-DismCmdlets.cab")
$pkgs.Add("$((Get-Location).Path)\ISOTEMP\OCs\en-US\WinPE-DismCmdlets_en-us.cab")
foreach ($pkg in $pkgs)
{
if (Test-Path $pkg -PathType Leaf)
{
Write-Host "Adding OS package $([IO.Path]::GetFileNameWithoutExtension($pkg))..."
Start-DismCommand -Verb Add-Package -ImagePath "$mountDirectory" -PackagePath $pkg | Out-Null
}
}
Write-Host "Saving changes..."
Start-DismCommand -Verb Commit -ImagePath "$mountDirectory" | Out-Null
# Perform customization tasks later
Write-Host "Beginning customizations..."
if ((Start-PECustomization -ImagePath "$mountDirectory" -arch $architecture -testStartNet $false) -eq $false)
{
Write-Host "Preinstallation Environment creation has failed in the PE customization phase. Discarding changes..."
Start-DismCommand -Verb Unmount -ImagePath "$mountDirectory" -Commit $false | Out-Null
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Unmounting image..."
Start-DismCommand -Verb Unmount -ImagePath "$mountDirectory" -Commit $true | Out-Null
Write-Host "PE generated successfully"
# Continue ISO customization
Write-Host "Copying image file. This can take some time..."
$totalTime = 0
if (Test-Path "$imgFile" -PathType Leaf)
{
$totalTime = Measure-Command { Copy-Item -Path "$imgFile" -Destination "$((Get-Location).Path)\ISOTEMP\media\sources\install.wim" -Verbose -Force -Recurse -Container }
}
if ($?)
{
Write-Host "The image file has been copied successfully. Time taken: $($totalTime.Minutes) minutes, $($totalTime.Seconds) seconds"
}
else
{
Write-Host "The image file has not been copied successfully."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Copying setup tools..."
Copy-Item -Path "$((Get-Location).Path)\PE_Helper.ps1" -Destination "$((Get-Location).Path)\ISOTEMP\media" -Verbose -Force -Recurse -Container -ErrorAction SilentlyContinue
New-Item -Path "$((Get-Location).Path)\ISOTEMP\media\files\diskpart" -ItemType Directory | Out-Null
Copy-Item -Path "$((Get-Location).Path)\files\diskpart\*.dp" -Destination "$((Get-Location).Path)\ISOTEMP\media\files\diskpart" -Verbose -Force -Recurse -Container -ErrorAction SilentlyContinue
Copy-Item -Path "$((Get-Location).Path)\files\README1ST.TXT" -Destination "$((Get-Location).Path)\ISOTEMP\media\README.TXT" -Verbose -Force -Recurse -Container -ErrorAction SilentlyContinue
New-Item -Path "$((Get-Location).Path)\ISOTEMP\media\Tools\DIM" -ItemType Directory | Out-Null
Copy-Item -Path "$((Get-Location).Path)\tools\DIM\*" -Destination "$((Get-Location).Path)\ISOTEMP\media\Tools\DIM" -Verbose -Force -Recurse -Container -ErrorAction SilentlyContinue
if (($unattendFile -ne "") -and (Test-Path "$unattendFile" -PathType Leaf))
{
Write-Host "Unattended answer file has been detected. Copying to ISO file..."
Copy-Item -Path "$unattendFile" -Destination "$((Get-Location).Path)\ISOTEMP\media\unattend.xml" -Verbose -Force -Recurse -Container -ErrorAction SilentlyContinue
}
Write-Host "Deleting temporary files..."
Remove-Item -Path "$((Get-Location).Path)\ISOTEMP\OCs" -Recurse -Force -ErrorAction SilentlyContinue
if ($?)
{
Write-Host "Temporary files have been deleted successfully"
}
else
{
Write-Host "Temporary files haven't been deleted successfully"
}
Write-Host "The ISO file structure has been successfully created. DISMTools will continue creating the ISO file automatically after 5 seconds."
Start-Sleep -Seconds 5
Write-Host "Creating ISO file..."
if ((New-WinPEIso -peToolsPath $peToolsPath -isoLocation $isoPath) -eq $false)
{
Write-Host "The ISO file has not been created successfully."
Write-Host "Deleting temporary files..."
Remove-Item -Path "$((Get-Location).Path)\ISOTEMP" -Recurse -Force -ErrorAction SilentlyContinue
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
Write-Host "Deleting temporary files..."
Remove-Item -Path "$((Get-Location).Path)\ISOTEMP" -Recurse -Force -ErrorAction SilentlyContinue
if ($mountDirectory.StartsWith("$env:TEMP"))
{
Remove-Item -Path "$mountDirectory" -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Host "The ISO file has been successfully created on the location you specified"
Start-Sleep -Seconds 5
exit 0
}
else
{
Write-Host "A Windows Assessment and Deployment Kit (ADK) could not be found on your system. Please install the Windows ADK for Windows 10 (or Windows 11), and its Windows PE plugin, and try again."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
}
else
{
Write-Host "A Windows Assessment and Deployment Kit (ADK) could not be found on your system. Please install the Windows ADK for Windows 10 (or Windows 11), and its Windows PE plugin, and try again."
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
}
catch
{
Write-Host "This process is unsuccessful as the following error occurred: $_"
Write-Host "`nPress ENTER to exit"
Read-Host | Out-Null
exit 1
}
}
function Copy-PEFiles
{
<#
.SYNOPSIS
Copies the Preinstallation Environment (PE) files to a temporary folder in the working directory
.PARAMETER peToolsPath
The path of the Preinstallation Environment (PE) tools. By default, this is "Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools"
.PARAMETER architecture
The architecture of the target Preinstallation Environment (PE). Valid options: x86, amd64, arm, arm64
.PARAMETER targetDir
The target directory to copy the Preinstallation Environment (PE) files to
.EXAMPLE
Copy-PEFiles -peToolsPath "C:\Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools" -architecture amd64 -targetDir "ISOTEMP"
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [string]$peToolsPath,
[Parameter(Mandatory = $true, Position = 1)] [PE_Arch]$architecture,
[Parameter(Mandatory = $true, Position = 2)] [string]$targetDir
)
try
{
$og_Location = (Get-Location).Path
Set-Location $peToolsPath
# Set required environment variables
Set-Item -Path "env:WinPERoot" -Value "$peToolsPath"
if ([Environment]::Is64BitOperatingSystem)
{
Set-Item -Path "env:OSCDImgRoot" -Value "$peToolsPath\..\Deployment Tools\amd64\Oscdimg"
}
else
{
Set-Item -Path "env:OSCDImgRoot" -Value "$peToolsPath\..\Deployment Tools\x86\Oscdimg"
}
$copype = Start-Process -FilePath "$peToolsPath\copype.cmd" -ArgumentList "$architecture `"$targetDir`"" -Wait -PassThru -NoNewWindow
if ($copype.ExitCode -eq 0)
{
Write-Host "PE files copied successfully."
}
else
{
Write-Host "Failed to copy PE files."
}
Set-Location $og_Location
return $($copype.ExitCode -eq 0)
}
catch
{
Write-Host "Failed to copy PE files."
return $false
}
}
function Copy-PEComponents
{
<#
.SYNOPSIS
Copies the Preinstallation Environment (PE) component files to a temporary folder in the working directory
.PARAMETER peToolsPath
The path of the Preinstallation Environment (PE) tools. By default, this is "Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools"
.PARAMETER architecture
The architecture of the target Preinstallation Environment (PE). Valid options: x86, amd64, arm, arm64
.PARAMETER targetDir
The target directory to copy the Preinstallation Environment (PE) component files to
.EXAMPLE
Copy-PEComponents -peToolsPath "C:\Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools" -architecture amd64 -targetDir "ISOTEMP"
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [string]$peToolsPath,
[Parameter(Mandatory = $true, Position = 1)] [PE_Arch]$architecture,
[Parameter(Mandatory = $true, Position = 2)] [string]$targetDir
)
try
{
New-Item -ItemType Directory -Path "$targetDir\OCs"
New-Item -ItemType Directory -Path "$targetDir\OCs\en-US"
$general_OCs = Get-ChildItem -Path "$peToolsPath\$($architecture.ToString())\WinPE_OCs" -File
$loc_OCs = Get-ChildItem -Path "$peToolsPath\$($architecture.ToString())\WinPE_OCs\en-US" -File
$copied = 0
$totalSize = 1
foreach ($file in $general_OCs)
{
Copy-Item -Path "$peToolsPath\$($architecture.ToString())\WinPE_OCs\$($file.Name)" -Destination "$targetDir\OCs" -Force -Container -PassThru -Verbose | ForEach-Object {
$copied = ($_.BytesTransferred / $totalSize) * 100
Write-Debug $copied
}
}
foreach ($file in $loc_OCs)
{
Copy-Item -Path "$peToolsPath\$($architecture.ToString())\WinPE_OCs\en-US\$($file.Name)" -Destination "$targetDir\OCs\en-US" -Force -Container -PassThru -Verbose | ForEach-Object {
$copied = ($_.BytesTransferred / $totalSize) * 100
Write-Debug $copied
}
}
Write-Host "PE components have been copied successfully."
return $true
}
catch
{
Write-Host "Failed to copy PE optional components."
return $false
}
}
function Start-PECustomization
{
<#
.SYNOPSIS
Starts the customization process of the Windows Preinstallation Environment (PE). This is a process required for the installer to work
.PARAMETER imagePath
The path of the mounted Windows PE image
.PARAMETER arch
The architecture of the target Windows PE image, which is used to customize the wallpaper
.PARAMETE testStartNet
Customizes the "startnet.cmd" file for WinPE testing
.EXAMPLE
Start-PECustomization -imagePath "<Mount Directory>" -arch "amd64" -testStartNet $false
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [string]$imagePath,
[Parameter(Mandatory = $true, Position = 1)] [PE_Arch]$arch,
[Parameter(Mandatory = $true, Position = 2)] [bool]$testStartNet
)
try
{
if (Test-Path "$imagePath\Windows\system32\winpe.jpg" -PathType Leaf)
{
try
{
Write-Host "CUSTOMIZATION STEP - Change Wallpaper" -BackgroundColor DarkGreen
Write-Host "Taking ownership of wallpaper..."
takeown /F "$imagePath\Windows\system32\winpe.jpg" /A
Write-Host "Setting Access Control Lists (ACLs) for wallpaper using icacls..."
icacls "$imagePath\Windows\system32\winpe.jpg" /grant "$(Get-LocalizedUsers -admins $true):(M)" | Out-Host
icacls "$imagePath\Windows\system32\winpe.jpg" /grant "$(Get-LocalizedUsers -admins $false):(M)" | Out-Host
Write-Host "Changing wallpaper..."
switch ($arch)
{
x86 {
Copy-Item -Path "$((Get-Location).Path)\backgrounds\winpe_x86.jpg" -Destination "$imagePath\Windows\system32\winpe.jpg" -Force
}
amd64 {
Copy-Item -Path "$((Get-Location).Path)\backgrounds\winpe_amd64.jpg" -Destination "$imagePath\Windows\system32\winpe.jpg" -Force
}
arm {
Copy-Item -Path "$((Get-Location).Path)\backgrounds\winpe_arm.jpg" -Destination "$imagePath\Windows\system32\winpe.jpg" -Force
}
arm64 {
Copy-Item -Path "$((Get-Location).Path)\backgrounds\winpe_arm64.jpg" -Destination "$imagePath\Windows\system32\winpe.jpg" -Force
}
default {
Copy-Item -Path "$((Get-Location).Path)\backgrounds\winpe_amd64.jpg" -Destination "$imagePath\Windows\system32\winpe.jpg" -Force
}
}
Write-Host "Wallpaper changed"
}
catch
{
Write-Host "Could not change wallpaper..."
}
}
try
{
Write-Host "CUSTOMIZATION STEP - Change Terminal Settings" -BackgroundColor DarkGreen
Write-Host "Opening registry..."
if (Open-PERegistry -regFile "$imagePath\Windows\system32\config\DEFAULT" -regName "PE_DefUser" -regLoad $true)
{
Write-Host "Setting window position..."
Set-ItemProperty -Path "HKLM:\PE_DefUser\Console" -Name "WindowPosition" -Value 6291480
Write-Host "Closing registry..."
Open-PERegistry -regFile "$imagePath\Windows\system32\config\DEFAULT" -regName "PE_DefUser" -regLoad $false
}
else
{
Write-Host "Could not modify terminal settings"
}
}
catch
{
Write-Host "Could not modify terminal settings"
}
if (($arch.ToString() -eq "x86") -or ($arch.ToString() -eq "amd64"))
{
try
{
Write-Host "CUSTOMIZATION STEP - Prepare System for Graphical Applications" -BackgroundColor DarkGreen
Write-Host "Opening registry..."
if (Open-PERegistry -regFile "$imagePath\Windows\system32\config\SOFTWARE" -regName "WINPESOFT" -regLoad $true)
{
Write-Host "Setting CLSID keys..."
$clsidKey = "HKLM\WINPESOFT\Classes\CLSID\{AE054212-3535-4430-83ED-D501AA6680E6}"
reg add "$clsidKey" /f
reg add "$clsidKey" /f /ve /t REG_SZ /d "Shell Name Space ListView"
reg add "$clsidKey\InprocServer32" /f
reg add "$clsidKey\InprocServer32" /f /ve /t REG_EXPAND_SZ /d "%SystemRoot%\system32\explorerframe.dll"
reg add "$clsidKey\InprocServer32" /f /v "ThreadingModel" /t REG_SZ /d "Apartment"
Write-Host "Closing registry..."
reg unload "HKLM\WINPESOFT"
if (-not $?)
{
$attempts = 0
do
{
$attempts += 1
Start-Sleep -Milliseconds 500
reg unload "HKLM\WINPESOFT"
} until ($?)
Write-Host "Registry closed successfully after $($attempts + 1) attempt(s)"
}
}
else
{
Write-Host "Could not prepare the system for graphical applications"
}
Write-Host "Copying DLL files..."
switch ($arch)
{
x86 {
Copy-Item -Path "\Windows\system32\ExplorerFrame.dll" -Destination "$imagePath\Windows\system32" -Force -Verbose
}
amd64 {
Copy-Item -Path "\Windows\system32\ExplorerFrame.dll" -Destination "$imagePath\Windows\system32" -Force -Verbose
Copy-Item -Path "\Windows\SysWOW64\ExplorerFrame.dll" -Destination "$imagePath\Windows\SysWOW64" -Force -Verbose
}
}
Write-Host "Creating folders..."
New-Item -Path "$imagePath\Windows\system32\config\systemprofile\Desktop" -ItemType Directory -Force
Write-Host "The target system is now ready for graphical applications"
}
catch
{
Write-Host "Could not prepare the system for graphical applications"
}
}
try
{
Write-Host "CUSTOMIZATION STEP - Change Startup Commands" -BackgroundColor DarkGreen
Write-Host "Changing startup commands..."
Copy-Item -Path "$((Get-Location).Path)\files\startup\startnet.cmd" -Destination "$imagePath\Windows\system32\startnet.cmd" -Force
if ($testStartNet)
{
$contents = Get-Content -Path "$imagePath\Windows\system32\startnet.cmd"
$contents[5] = "set debug=2"
Set-Content -Path "$imagePath\Windows\system32\startnet.cmd" -Value $contents -Force
}
Copy-Item -Path "$((Get-Location).Path)\files\startup\StartInstall.ps1" -Destination "$imagePath\StartInstall.ps1" -Force
Write-Host "Startup commands changed"
}
catch
{
Write-Host "Could not change startup commands"
}
Write-Host "CUSTOMIZATION STEP - Set Scratch Size" -BackgroundColor DarkGreen
Write-Host "Setting scratch size..."
dism /English /image="$imagePath" /set-scratchspace=512 | Out-Host
if ($?)
{
Write-Host "Scratch size set."
}
else
{
Write-Host "Scratch size could not be set."
}
return $true
}
catch
{
return $false
}
}
function Get-LocalizedUsers
{
<#
.SYNOPSIS
Gets a localized user group representation for ICACLS commands
.PARAMETER admins
Determines whether to get a localized user group representation for the Administrators user group
.OUTPUTS
A string containing the localized user group
.EXAMPLE
Get-LocalizedUsers -admins $true
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [bool]$admins
)
if ($admins)
{
return (Get-LocalGroup | Where-Object { $_.SID.Value -like "S-1-5-32-544" }).Name
}
else
{
return (Get-LocalGroup | Where-Object { $_.SID.Value -like "S-1-5-32-545" }).Name
}
}
function Open-PERegistry
{
<#
.SYNOPSIS
Performs actions with the registry hives of the Windows Preinstallation Environment (PE)
.PARAMETER regFile
The file of the registry hive to load
.PARAMETER regName
The name to use when loading a registry hive
.PARAMETER regLoad
Determine whether to load or unload a registry hive
.EXAMPLE
Open-PERegistry -regFile "<Mount Directory>\Windows\system32\config\SOFTWARE" -regName "PESoft" -regLoad $true
.EXAMPLE
Open-PERegistry -regFile "" -regName "PESoft" -regLoad $false
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [string]$regFile,
[Parameter(Mandatory = $true, Position = 1)] [string]$regName,
[Parameter(Mandatory = $true, Position = 2)] [bool]$regLoad
)
try
{
if ($regLoad)
{
reg load "HKLM\$regName" "$regFile"
}
else
{
reg unload "HKLM\$regName"
}
Write-Host "Registry action performed successfully"
return $true
}
catch
{
return $false
}
}
function New-WinPEIso
{
<#
.SYNOPSIS
Creates the target ISO file defined either in the GUI or via the command line
.PARAMETER peToolsPath
The path of the Preinstallation Environment (PE) tools. By default, this is "Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools"
.PARAMETER isoLocation
The path of the target ISO file
.EXAMPLE
New-WinPEIso -peToolsPath "C:\Program Files\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools" -isoLocation "C:\PreInstEnv.iso"
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [string]$peToolsPath,
[Parameter(Mandatory = $true, Position = 1)] [string]$isoLocation
)
try
{
if (Test-Path "$isoLocation" -PathType Leaf)
{
# Check if the ISO file exists
Remove-Item -Path "$isoLocation" -Force
}
if ([Environment]::Is64BitOperatingSystem)
{
Set-Item -Path "env:NewPath" -Value "$peToolsPath\..\Deployment Tools\amd64\Oscdimg"
}
else
{
Set-Item -Path "env:NewPath" -Value "$peToolsPath\..\Deployment Tools\x86\Oscdimg"
}
if (Test-Path "$((Get-Location).Path)\ISOTEMP\fwfiles\etfsboot.com" -PathType Leaf)
{
Write-Host "Generating ISO file with BIOS and UEFI compatibility..."
$bootData = "2#p0,e,b`"$((Get-Location).Path)\ISOTEMP\fwfiles\etfsboot.com`"#pEF,e,b`"$((Get-Location).Path)\ISOTEMP\fwfiles\efisys.bin`""
}
else
{
Write-Host "Generating ISO file with UEFI compatibility..."
$bootData = "1#pEF,e,b`"$((Get-Location).Path)\ISOTEMP\fwfiles\efisys.bin`""
}
$oscdimgProc = Start-Process "$env:NewPath\oscdimg.exe" -ArgumentList "-lDISMTools_PE -bootdata:$bootData -u2 -udfver102 `"$((Get-Location).Path)\ISOTEMP\media`" `"$isoLocation`"" -Wait -PassThru -NoNewWindow
if ($oscdimgProc.ExitCode -eq 0)
{
Write-Host "ISO generation has completed successfully."
}
else
{
Write-Host "Failed to generate an ISO file."
}
return $($oscdimgProc.ExitCode -eq 0)
}
catch
{
Write-Host "Failed to generate an ISO file."
return $false
}
}
function Start-OSApplication
{
<#
.SYNOPSIS
Starts the OS installation stage
#>
# Detect if it's run on Windows PE
if ((Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID') -ne "WindowsPE")
{
Write-Host "This procedure must be run on Windows PE only."
return
}
if ((Get-ChildItem -Path "$((Get-Location).Path)sources\*.wim" -Exclude "boot.wim").Count -lt 1)
{
Write-Host "No Windows image has been found on this drive. An installation image is required. Exiting..."
exit 1
}
New-Item -Path "X:\files\diskpart" -ItemType Directory -Force | Out-Null
$drive = Get-Disks
if ($drive -eq "ERROR")
{
Write-Host "Script has failed."
return
}
Write-Host "Selected disk: disk $($drive)"
$partition = Get-Partitions $drive
if ($partition -eq "B")
{
do {
$drive = Get-Disks
if ($drive -eq "ERROR")
{
Write-Host "Script has failed."
return
}
Write-Host "Selected disk: disk $($drive)"
$partition = Get-Partitions $drive
} until ($partition -ne "B")
}
if ($partition -eq 0)
{
$msg = "This will perform disk configuration changes on disk $drive. THIS WILL DELETE ALL PARTITIONS IN IT. IF YOU ARE NOT WILLING TO LOSE DATA, DO NOT CONTINUE."
}
else
{
$msg = "This will perform disk configuration changes on partition $partition. THIS WILL FORMAT IT IT. IF YOU ARE NOT WILLING TO LOSE DATA, DO NOT CONTINUE."
}
Write-Host $msg -BackgroundColor Black -ForegroundColor Yellow
$choice = Read-Host "Are you sure you want to continue (Y/N)"
if ($choice -ne "Y")
{
do
{
$partition = Get-Partitions $drive
if ($partition -eq "B")
{
do {
$drive = Get-Disks
if ($drive -eq "ERROR")
{
Write-Host "Script has failed."
return
}
Write-Host "Selected disk: disk $($drive)"
$partition = Get-Partitions $drive
} until ($partition -ne "B")
}
if ($partition -eq 0)
{
$msg = "This will perform disk configuration changes on disk $drive. THIS WILL DELETE ALL PARTITIONS IN IT. IF YOU ARE NOT WILLING TO LOSE DATA, DO NOT CONTINUE.`n"
}
else
{
$msg = "This will perform disk configuration changes on partition $partition. THIS WILL FORMAT IT. IF YOU ARE NOT WILLING TO LOSE DATA, DO NOT CONTINUE.`n"
}
Write-Host $msg -BackgroundColor Black -ForegroundColor Yellow
$choice = Read-Host "Are you sure you want to continue (Y/N)"
} until ($choice -eq "Y")
}
$driveLetter = ""
if ($partition -eq 0)
{
$driveLetter = "C"
# Proceed with default disk configuration
Write-DiskConfiguration $drive $true $partition
}
else
{
# Proceed with custom disk configuration
Write-DiskConfiguration $drive $false $partition
$volLister = @'
lis vol
exit
'@
$volLister | Out-File "X:\files\diskpart\dp_vols.dp" -Force -Encoding utf8
diskpart /s "X:\files\diskpart\dp_vols.dp" | Out-Host
$driveLetter = Read-Host "Specify a drive letter"
if ($driveLetter -eq "")
{
do
{
Write-Host "No drive letter has been specified."
$driveLetter = Read-Host "Specify a drive letter"
} until ($driveLetter -ne "")
}
}
Write-Host "Creating page file for Windows PE..."
wpeutil createpagefile /path="$($driveLetter):\WinPEpge.sys" /size=256
$wimFile = Get-WimIndexes
$serviceableArchitecture = (((Get-CimInstance -Class Win32_Processor | Where-Object { $_.DeviceID -eq "CPU0" }).Architecture) -eq (Get-WindowsImage -ImagePath "$($wimFile.wimPath)" -Index $wimFile.index).Architecture)
Write-Host "Applying Windows image. This can take some time..."
if ((Start-DismCommand -Verb Apply -ImagePath "$($driveLetter):\" -WimFile "$($wimFile.wimPath)" -WimIndex $wimFile.index) -eq $true)
{
Write-Host "The Windows image has been applied successfully."
}
else
{
Write-Host "Failed to apply the Windows image."
}
if ($serviceableArchitecture) { Set-Serviceability -ImagePath "$($driveLetter):\" } else { Write-Host "Serviceability tests will not be run: the image architecture and the PE architecture are different." }
if (Test-Path "$((Get-Location).Path)\unattend.xml" -PathType Leaf)
{
Write-Host "A possible unattended answer file has been detected, applying it... " -NoNewline
if ((Start-DismCommand -Verb UnattendApply -ImagePath "$($driveLetter):\" -unattendPath "$((Get-Location).Path)\unattend.xml") -eq $true)
{
Write-Host "SUCCESS" -ForegroundColor White -BackgroundColor DarkGreen
}
else
{
Write-Host "FAILURE" -ForegroundColor Black -BackgroundColor DarkRed
}
}
$driverPath = "$([IO.Path]::GetPathRoot([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)))DT_InstDrvs.txt"
if ((Test-Path "$($driveLetter):\`$DISMTOOLS.~LS") -and ($serviceableArchitecture) -and (Test-Path -Path $driverPath -PathType Leaf))
{
Write-Host "Adding drivers to the target image..."
# Add drivers that were previously added to the Windows PE using the DIM
$drivers = (Get-Content -Path $driverPath | Where-Object { $_.Trim() -ne "" })
foreach ($driver in $drivers)
{
if (Test-Path -Path "$driver" -PathType Leaf)
{
Write-Host "Adding driver `"$driver`"... " -NoNewline
if ((Start-DismCommand -Verb Add-Driver -ImagePath "$($driveLetter):\" -DriverAdditionFile "$driver" -DriverAdditionRecurse $false) -eq $true)
{
Write-Host "SUCCESS" -ForegroundColor White -BackgroundColor DarkGreen
}
else
{
Write-Host "FAILURE" -ForegroundColor Black -BackgroundColor DarkRed
}
}
}
# Perform serviceability tests one more time
if ($serviceableArchitecture) { Set-Serviceability -ImagePath "$($driveLetter):\" } else { Write-Host "Serviceability tests will not be run: the image architecture and the PE architecture are different." }
}
if (Test-Path "$($driveLetter):\`$DISMTOOLS.~LS")
{
Remove-Item -Path "$($driveLetter):\`$DISMTOOLS.~LS" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
}
New-BootFiles -drLetter $driveLetter -bootPart "auto" -diskId $drive -cleanDrive $($partition -eq 0)
Start-Sleep -Milliseconds 250
# Show message before rebooting system
Write-Host "The first stage of Setup has completed, and your system will reboot automatically."
Write-Host "If there are any bootable devices, remove those before proceeding, as your system may boot to this environment again."
Write-Host "When your computer restarts, Setup will continue."
Show-Timeout -Seconds 10
wpeutil reboot
}
function Get-SystemArchitecture
{
# Detect CPU architecture and compare with list
switch (((Get-CimInstance -Class Win32_Processor | Where-Object { $_.DeviceID -eq "CPU0" }).Architecture).ToString())
{
"0"{
return "i386"
}
"1"{
return "mips"
}
"2"{
return "alpha"
}
"3"{
return "powerpc"
}
"5"{
return "arm"
}
"6"{
return "ia64"
}
"9"{
return "amd64"
}
"12" {
return "arm64"
}
default {
return ""
}
}
return ""
}
function Get-Disks
{
<#
.SYNOPSIS
Gets the available disks with DiskPart
#>
# Show disk list with diskpart
if (Test-Path .\files\diskpart\dp_listdisk.dp -PathType Leaf)
{
diskpart /s ".\files\diskpart\dp_listdisk.dp" | Out-Host
}
else
{
Write-Host "DISKPART scripts not found."
return "ERROR"
}
# Show additional tools
Write-Host "- To load drivers, type `"DIM`" and press ENTER`n"
$destDisk = Read-Host -Prompt "Please choose the disk to apply the image to"
$destDrive = -1
try
{
$destDrive = [int]$destDisk
return $destDrive
}
catch
{
switch ($destDisk)
{
"DIM" {
# Get CPU architecture and launch Driver Installation Module
$supportedArchitectures = [List[string]]::new()
$supportedArchitectures.Add("i386")
$supportedArchitectures.Add("amd64")
$systemArchitecture = Get-SystemArchitecture
if ($supportedArchitectures.Contains($systemArchitecture))
{
if (Test-Path -Path "$([IO.Path]::GetPathRoot([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)))Tools\DIM\$systemArchitecture\DT-DIM.exe")
{
Clear-Host
Write-Host "Starting the Driver Installation Module...`n`nYou will go back to the disk selection screen after closing the program."
Start-Process -FilePath "$([IO.Path]::GetPathRoot([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)))Tools\DIM\$systemArchitecture\DT-DIM.exe" -Wait
}
}
Get-Disks
}
default {
Write-Host "Please specify a number and try again.`n"
Get-Disks
}
}
}
}
function Get-Partitions
{
<#
.SYNOPSIS
Gets the partitions of a drive using DiskPart
.PARAMETER driveNum
The drive number
.EXAMPLE
Get-Partitions 0
#>
param (
[Parameter(Mandatory = $true)] [int]$driveNum
)
$partLister = @'
sel dis <REPLACEME>
lis par
exit
'@
$partLister = $partLister.Replace("<REPLACEME>", $driveNum).Trim()
$partLister | Out-File -FilePath "X:\files\diskpart\dp_listpart.dp" -Force -Encoding utf8
$part = -1
diskpart /s "X:\files\diskpart\dp_listpart.dp" | Out-Host
Write-Host ""
Write-Host "- If the selected disk contains no partitions, press ENTER. Otherwise, type a partition number."
Write-Host "- If you have selected the wrong disk, type `"B`" now and press ENTER`n"
$part = Read-Host -Prompt "Please choose the partition to apply the image to"
if ($part -eq -1)
{
return $part
}
elseif ($part -eq "B")
{
return $part
}
else
{
try
{
$partition = [int]$part
return $partition
}
catch
{
Write-Host "Please specify a number and try again.`n"
Get-Partitions $driveNum
}
}
}
function Write-DiskConfiguration
{
<#
.SYNOPSIS
Writes disk configuration using DiskPart
.PARAMETER diskid
The index number of the disk
.PARAMETER cleanDrive
Determine whether to clean the entire drive. Useful for single-boot scenarios
.PARAMETER partId
The partition number
.NOTES
The partition ID is 0 if the user decides to clean a drive
.EXAMPLE
Write-DiskConfiguration -diskid 0 -cleanDrive $true -partId 0
.EXAMPLE
Write-DiskConfiguration -diskid 0 -cleanDrive $false -partId 2
#>
param (
[Parameter(Mandatory = $true, Position = 0)] [int]$diskid,
[Parameter(Mandatory = $true, Position = 1)] [bool]$cleanDrive,
[Parameter(Mandatory = $true, Position = 2)] [int]$partId
)
Write-Host "Writing disk configuration. Please wait..."
if ($cleanDrive)
{
$formatter = @'
sel dis #DISKID#
cle
#GPTPART#
#MBRPART#
exit
'@
$formatter_gpt = @'
conv gpt
cre par efi size=512
for fs=fat32 quick label="System"
ass letter W