-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathCreate-EXEFrom.ps1
665 lines (578 loc) · 28 KB
/
Create-EXEFrom.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
<#
.SYNOPSIS
Convert a PowerShell script into a deployable exe using iexpress.
.DESCRIPTION
Takes one PowerShell script and any number of supplementary files and create an exe using Windows's built in iexpress program.
If you use one of the parameters that allows you to provide a folder, the script will zip that folder and add it as a supplemental file.
Upon running the exe, the directory will first be unzipped and made available with the same structure, retaining relative path calls.
Verbose output is available for most of the processes in this script if you call it using the -Verbose parameter.
.PARAMETER PSScriptPath
Path string to PowerShell script that you want to use as the first thing iexpress calls when the exe is run.
If blank, you will be prompted with a file browse dialog where you can select a file.
.PARAMETER SupplementalFilePaths
Array of comma separated supplemental file paths that you want to include as resources.
.PARAMETER SelectSupplementalFiles
Use this flag to be prompted to select the supplementary files in an Open File Dialog.
.PARAMETER SupplementalDirectoryPath
Path to a directory that will be zipped and added as a supplementary file.
When the exe is run, this script will first be unzipped and all files are available.
.PARAMETER SelectSupplementalDirectory
Use this flag to be prompted to select a directory in an Open File Dialog that will be zipped and added as a supplementary file.
When the exe is run, this script will first be unzipped and all files are available.
.PARAMETER KeepTempDir
Keep the temp directory around after the exe is created. It is available at the root of C:.
.PARAMETER x86
Use the 32-bit iexpress path so that 32-bit PowerShell is consequently called.
.PARAMETER SigningCertificate
Sign all PowerShell scripts and subsequent executable with the defined certificate.
Expected format of Cert:\CurrentUser\My\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.PARAMETER OutputDirectory
Move the completed executable to the defined directory.
.OUTPUTS
An exe file in the same directory as the ps1 script you specify
.EXAMPLE
.\Create-EXEFrom.ps1 -PSScriptPath .\test.ps1 -SupplementalFilePaths '..\test2.ps1', .\ps1toexe.ps1
# Creates an exe using the provided PowerShell script and supplemental files.
.EXAMPLE
.\Create-EXEFrom.ps1 -SelectSupplementalFiles
# Prompts the user to select the PowerShell script and supplemental files using an Open File Dialog.
.EXAMPLE
.\Create-EXEFrom.ps1 -SupplementalDirectoryPath 'C:\Temp\MyTestDir' -KeepTempDir
# Zips MyTestDir and attaches it to the exe. When the exe is run, but before the user's script gets run,
# it will be extracted to the same directory as the user's script. Temp directory used during exe creation
# will be left intact for user inspection or debugging purposes.
.NOTES
Created by Nick Rodriguez
Modified by Etcha-Sketch - Added PKI signature support for script and executables and added output directory functionality. Changed default architecture to x64
Requires iexpress, which is included in most versions of Windows (https://en.wikipedia.org/wiki/IExpress).
#>
[CmdletBinding(DefaultParameterSetName = 'NoSupplementalFiles')]
param (
[Parameter(Mandatory=$false)]
[ValidateScript({
if ((Get-Item $_).Extension -ne '.ps1') {
throw "[$_] is not a PowerShell script (ps1)."
} else { $true }
})]
[string]
$PSScriptPath,
[Parameter(Mandatory=$false, ParameterSetName = 'SpecifyFiles')]
[ValidateScript({
foreach ($FilePath in $_) {
if (-not (Get-Item $FilePath)) {
throw "[$FilePath] was not found."
} else { $true }
}
})]
[string[]]
$SupplementalFilePaths,
[Parameter(Mandatory=$false, ParameterSetName = 'SelectFiles')]
[switch]
$SelectSupplementalFiles,
[Parameter(Mandatory=$false, ParameterSetName = 'SpecifyDirectory')]
[ValidateScript({
if (-not (Get-Item $_).PSIsContainer) {
throw "[$_] is not a directory."
} else { $true }
})]
[string]
$SupplementalDirectoryPath,
[Parameter(Mandatory=$false, ParameterSetName = 'SelectDirectory')]
[switch]
$SelectSupplementalDirectory,
[Parameter(Mandatory=$false)]
[switch]
$KeepTempDir,
[Parameter(Mandatory=$false)]
[switch]
$x86,
[Parameter(Mandatory=$false)]
[ValidateScript({
if (Test-Path $_) {
if ((Get-Item $_).HasPrivateKey -ne $true) {
throw "[$_] You do not have the corresponding private key to sign with this certificate."
} else { $true }
}
else {
throw "[$_] Cannot find the certificate."
}
})]
[string]
$SigningCertificate,
[Parameter(Mandatory=$false)]
[ValidateScript({
if (-not (Get-Item $_).PSIsContainer) {
throw "[$_] is not a directory."
} else { $true }
})]
[string]
$OutputDirectory
)
begin {
# use 64-bit iexpress as everything should be 64-bit by now, you can specify -x86 for compatibility reasons.
if ($x86) {
$IExpress = "C:\WINDOWS\SysWOW64\iexpress"
} else {
$IExpress = "C:\WINDOWS\System32\iexpress"
}
function Get-File {
[CmdletBinding()]
[OutputType([psobject[]])]
param (
[Parameter(Mandatory=$false)]
[switch]
$SupplementalFiles
)
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
# PSScriptRoot will be null if run from the ISE or PS Version < 3.0
$OpenFileDialog.InitialDirectory = $PSScriptRoot
if ($SupplementalFiles) {
$OpenFileDialog.Title = 'Select one or more files'
$OpenFileDialog.filter = "All Files| *.*"
$OpenFileDialog.Multiselect = 'true'
} else {
$OpenFileDialog.Title = 'Select a file'
$OpenFileDialog.filter = "PowerShell (*.ps1)| *.ps1"
}
$OpenFileDialog.ShowHelp = $true
$OpenFileDialog.ShowDialog() | Out-Null
try {
if ($SupplementalFiles) {
foreach ($FileName in $OpenFileDialog.FileNames) { Get-Item $FileName }
} else {
Get-Item $OpenFileDialog.FileName
}
} catch {
Write-Warning 'Open File Dialog was closed or cancelled without selecting any files'
}
}
function Get-Directory {
[CmdletBinding()]
[OutputType([psobject])]
param()
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenDirectoryDialog = New-Object Windows.Forms.FolderBrowserDialog
$OpenDirectoryDialog.ShowDialog() | Out-Null
try {
Get-Item $OpenDirectoryDialog.SelectedPath
} catch {
Write-Warning 'Open Directory Dialog was closed or cancelled without selecting a Directory'
}
}
function Test-NETVersion {
$NETPath = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client'
# Get the .NET version, or leave null if less than 4
try { $NETVersion = (Get-ItemProperty $NETPath -ErrorAction SilentlyContinue).Version } catch { }
# Return true if .NET client is at least version 4.5
if ($NETVersion -ge 4.5) {
Write-Verbose 'Client .NET is version 4.5 or greater'
$true
} else {
Write-Verbose 'Client .NET is less than version 4.5'
$false
}
}
function Test-PSVersion {
# Return true if PowerShell is at least version 3
if ($PSVersionTable.PSVersion.Major -ge 3) {
Write-Verbose 'PowerShell is version 3 or greater'
$true
} else {
Write-Verbose 'PowerShell is less than version 3'
$false
}
}
# This zip method is not as reliable, and slower, but we must use it if .NET 4.5+ is not available
function Zip-FileOld {
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory=$true)]
[string]
$SourceDirectoryPath,
[Parameter(Mandatory=$true)]
[string]
$DestinationDirectoryPath
)
Write-Verbose 'Using shell application based zip process'
# Get the full path of the source zip archive
$SourceDirectoryFullPath = (Get-Item $SourceDirectoryPath).FullName
Write-Verbose "Full path of source directory: $SourceDirectoryFullPath"
$TargetZipPath = "$DestinationDirectoryPath\$($SourceDirectoryFullPath.Split('\')[-1]).zip"
# Create empty zip file that is not read only
Set-Content $TargetZipPath ([byte[]] @(80, 75, 5, 6 + (,0 * 18))) -Encoding Byte
(Get-Item $TargetZipPath).IsReadOnly = $false
$Shell = New-Object -ComObject Shell.Application
$ZipFile = $Shell.NameSpace($TargetZipPath)
Write-Verbose "Zip file location: $TargetZipPath"
# User Force parameter to make sure we get hidden items too
Get-ChildItem $SourceDirectoryFullPath -Force | ForEach-Object {
# Skip empty directories
if (($_.Mode -like 'd-----') -and (-not (Get-ChildItem $_.FullName | Measure-Object).Count)) {
Write-Verbose "$($_.Name) is an empty directory; skipping"
} else {
# Copy file into zip
$ZipFile.CopyHere($_.FullName)
Write-Verbose "Copied $($_.Name) into zip"
# Limitation of shell, wait to make sure each file is copied before continuing
while ($ZipFile.Items().Item($_.Name) -eq $null) {
Start-Sleep -Milliseconds 500
}
}
}
# Return the path of the zip file
$TargetZipPath
}
# This zip method requires .NET 4.5+, so we check for that
function Zip-File {
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory=$true)]
[string]
$SourceDirectoryPath,
[Parameter(Mandatory=$true)]
[string]
$DestinationDirectoryPath
)
Write-Verbose 'Using assembly based zip process'
# Get the full path of the source zip archive
$SourceDirectoryFullPath = (Get-Item $SourceDirectoryPath).FullName
Write-Verbose "Full path of source directory: $SourceDirectoryFullPath"
$TargetZipPath = "$DestinationDirectoryPath\$($SourceDirectoryFullPath.Split('\')[-1]).zip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[IO.Compression.ZipFile]::CreateFromDirectory($SourceDirectoryFullPath, $TargetZipPath)
Write-Verbose "Zip file location: $TargetZipPath"
# Return the path of the zip file
$TargetZipPath
}
# We need to make this function available to the exe, so save it to a variable for later
$UnZipFunction = {
function UnZip-File {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$SourceZipPath
)
$Shell = New-Object -ComObject Shell.Application
# Get the full path of the source zip archive
$SourceZipFullPath = (Get-Item $SourceZipPath).FullName
Write-Verbose "Full path of zip archive: $SourceZipFullPath"
# Create the destination folder with the same name as the source
$DestinationDirectory = (New-Item ($SourceZipFullPath -replace '.zip', '') -ItemType Directory -Force).FullName
Write-Verbose "Destination for zip archive contents: $DestinationDirectory"
# UnZip files answering yes to all prompts
$Shell.NameSpace($DestinationDirectory).CopyHere($Shell.NameSpace($SourceZipFullPath).Items(), 16)
}
}
}
process {
# If no PowerShell script specified, prompt the user to select one
if ($PSScriptPath) {
$PSScriptName = (Get-Item $PSScriptPath).Name
} else {
try {
$PSScriptPath = (Get-File).FullName
$PSScriptName = $PSScriptPath.Split('\')[-1]
} catch { exit }
}
Write-Verbose "PowerShell script selected: $PSScriptPath"
# If signing certificate defined, generate objects to be used to sign subsequent scrips/executables.
if (($SigningCertificate -ne "") -and ($SigningCertificate -ne $null)) {
$SignFiles = $true
Write-Verbose "Signing Certificate defined, will detect and sign any unsigned supplemental PowerShell scripts and final executable."
$certificateobject = Get-Item $SigningCertificate
$certificatethumb = $certificateobject.Thumbprint
} else {
$SignFiles = $false
}
# Name of the extensionless target, replace spaces with underscores
$Target = ($PSScriptName -replace '.ps1', '') -replace " ", '_'
# Get the directory the script was found in
$ScriptRoot = $PSScriptPath.Substring(0, $PSScriptPath.LastIndexOf('\'))
# Create temp directory to store all files
$Temp = New-Item "C:\$Target$(Get-Date -Format "HHmmss")" -ItemType Directory -Force
Write-Verbose "Using temp directory $Temp"
# Copy the PowerShell script to our temp directory
if ($SignFiles) {
Write-Verbose "Checking primary PowerShell scripts for signature."
if (((Get-AuthenticodeSignature $PSScriptPath).Status) -ne 'Valid') {
Write-Verbose "$($PSScriptPath) is not signed, signing with certificate thumb print: $($certificatethumb)"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $PSScriptPath | Out-Null
if ((((Get-AuthenticodeSignature -FilePath $PSScriptPath).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($PSScriptPath) failed"
} else {
Write-verbose "$($PSScriptPath) signing successful."
}
} else {
Write-verbose "$($PSScriptPath) already has a valid signature."
}
}
Copy-Item $PSScriptPath $Temp
Write-Verbose "Using Parameter Set: $($PSCmdlet.ParameterSetName)"
if ($PSCmdlet.ParameterSetName -eq 'NoSupplementalFiles') {
Write-Verbose 'Not using supplemental files'
} else {
if ($PSCmdlet.ParameterSetName -eq 'SelectFiles') {
# Prompt user to select supplemental files
$SupplementalFilePaths = (Get-File -SupplementalFiles).FullName
$SupplementalFiles = (Get-Item $SupplementalFilePaths).Name
Write-Verbose "Supplemental files: `n$SupplementalFilePaths"
if ($SignFiles) {
# Determine if any of the specified files are PowerShell scripts and sign them if they are.
Write-Verbose "Checking supplemental files for any PowerShell scripts and signing them."
foreach ($SupplementalFile in $SupplementalFilePaths) {
if ((Get-item $SupplementalFile).Extension -eq '.ps1') {
$SupplementalScript = Get-item $SupplementalFile
if (((Get-AuthenticodeSignature $SupplementalScript).Status) -ne 'Valid') {
Write-Verbose "$($SupplementalScript) is not signed, signing with certificate thumb print: $($certificatethumb)"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $SupplementalScript | Out-Null
if ((((Get-AuthenticodeSignature -FilePath $SupplementalScript).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($SupplementalScript) failed"
} else {
Write-verbose "$($SupplementalScript) signing successful."
}
} else {
Write-verbose "$($SupplementalScript) already has a valid signature."
}
}
}
}
# Copy supplemental files to temp directory
Copy-Item $SupplementalFilePaths $Temp
} elseif ($PSCmdlet.ParameterSetName -eq 'SpecifyFiles') {
# Get the paths of the files the user supplied
$SupplementalFilePaths = (Get-Item $SupplementalFilePaths).FullName
$SupplementalFiles = (Get-Item $SupplementalFilePaths).Name
Write-Verbose "Supplemental files: `n$SupplementalFilePaths"
if ($SignFiles) {
# Determine if any of the specified files are PowerShell scripts and sign them if they are.
Write-Verbose "Checking supplemental files for any PowerShell scripts and signing them."
foreach ($SupplementalFile in $SupplementalFilePaths) {
if ((Get-item $SupplementalFile).Extension -eq '.ps1') {
$SupplementalScript = Get-item $SupplementalFile
if (((Get-AuthenticodeSignature $SupplementalScript).Status) -ne 'Valid') {
Write-Verbose "$($SupplementalScript) is not signed, signing with certificate thumb print: $($certificatethumb)"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $SupplementalScript | Out-Null
if ((((Get-AuthenticodeSignature -FilePath $SupplementalScript).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($SupplementalScript) failed"
} else {
Write-verbose "$($SupplementalScript) signing successful."
}
} else {
Write-verbose "$($SupplementalScript) already has a valid signature."
}
}
}
}
# Copy supplemental files to temp directory
Copy-Item $SupplementalFilePaths $Temp
} elseif ($PSCmdlet.ParameterSetName -eq 'SelectDirectory') {
# Prompt user to select supplemental directory
$SupplementalDirectoryPath = (Get-Directory).FullName
Write-Verbose "Supplemental directory: $SupplementalDirectoryPath"
if ($SignFiles) {
# Find and sign any unsigned PowerShell scripts in the supplemental directory and sign them before zipping.
foreach ($SupplementalScript in (Get-ChildItem "$($SupplementalDirectoryPath)\*.ps1" -recurse)) {
if (((Get-AuthenticodeSignature $SupplementalScript).Status) -ne 'Valid') {
Write-Verbose "$($SupplementalScript) is not signed, signing with certificate thumb print: $($certificatethumb)"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $SupplementalScript | Out-Null
if ((((Get-AuthenticodeSignature -FilePath $SupplementalScript).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($SupplementalScript) failed"
} else {
Write-verbose "$($SupplementalScript) signing successful."
}
} else {
Write-verbose "$($SupplementalScript) already has a valid signature."
}
}
}
if ((Test-NETVersion) -and (Test-PSVersion)) {
$SupplementalFilePaths = Zip-File -SourceDirectoryPath $SupplementalDirectoryPath -DestinationDirectoryPath $Temp
} else {
$SupplementalFilePaths = Zip-FileOld -SourceDirectoryPath $SupplementalDirectoryPath -DestinationDirectoryPath $Temp
}
$SupplementalFiles = $SupplementalFilePaths.Split('\')[-1]
# Move supplemental zip to temp directory
Move-Item $SupplementalFilePaths $Temp
} elseif ($PSCmdlet.ParameterSetName -eq 'SpecifyDirectory') {
# Get the path of the directory the user supplied
$SupplementalDirectoryPath = $SupplementalDirectoryPath.TrimEnd('\')
$SupplementalDirectoryPath = (Get-Item $SupplementalDirectoryPath).FullName
Write-Verbose "Supplemental directory: $SupplementalDirectoryPath"
if ($SignFiles) {
# Find and sign any unsigned PowerShell scripts in the supplemental directory and sign them before zipping.
foreach ($SupplementalScript in (Get-ChildItem "$($SupplementalDirectoryPath)\*.ps1" -recurse)) {
if (((Get-AuthenticodeSignature $SupplementalScript).Status) -ne 'Valid') {
Write-Verbose "$($SupplementalScript) is not signed, signing with certificate thumb print: $($certificatethumb)"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $SupplementalScript | Out-Null
if ((((Get-AuthenticodeSignature -FilePath $SupplementalScript).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($SupplementalScript) failed"
} else {
Write-verbose "$($SupplementalScript) signing successful."
}
} else {
Write-verbose "$($SupplementalScript) already has a valid signature."
}
}
}
if ((Test-NETVersion) -and (Test-PSVersion)) {
$SupplementalFilePaths = Zip-File -SourceDirectoryPath $SupplementalDirectoryPath -DestinationDirectoryPath $Temp
} else {
$SupplementalFilePaths = Zip-FileOld -SourceDirectoryPath $SupplementalDirectoryPath -DestinationDirectoryPath $Temp
}
$SupplementalFiles = $SupplementalFilePaths.Split('\')[-1]
# Move supplemental zip to temp directory
Move-Item $SupplementalFilePaths $Temp
}
}
# If creating 64-bit exe, append to name to clarify
if ($x86) {
$EXE = "$ScriptRoot\$Target (x86).exe"
} else {
$EXE = "$ScriptRoot\$Target (x64).exe"
}
# Determine if the EXE target already exists, and if it does prompt the user on how to continue.
if (($OutputDirectory -eq $null) -or ($OutputDirectory -eq "")) {
if (Test-Path $EXE) {
Write-Output "$($EXE) already exists, would you like to replace it?"
$ClearExisting = Read-Host "[y]/n"
if (($ClearExisting.ToUpper()) -eq 'N') {
Write-Host "Please re-run after you have cleaned up the destination directory." -ForegroundColor Red
Start-sleep -seconds 30
Remove-Item $Temp -Recurse -Force
break
} else {
Remove-item $EXE -Force | Out-Null
}
}
$FinalOutputEXE = $EXE
} else {
$FinalOutputEXE = "$($OutputDirectory)\$($EXE.split('\')[-1])"
if (Test-Path $EXE) {
$EXE = $EXE.replace(".exe","-$(Get-Random -Minimum 1000000000 -Maximum 9999999999).exe")
}
if (Test-Path $FinalOutputEXE) {
Write-Output "$($FinalOutputEXE) already exists, would you like to replace it?"
$ClearExisting = Read-Host "[y]/n"
if (($ClearExisting.ToUpper()) -eq 'N') {
Write-Host "Please re-run after you have cleaned up the destination directory." -ForegroundColor Red
Start-sleep -seconds 30
Remove-Item $Temp -Recurse -Force
break
} else {
Remove-item $FinalOutputEXE -Force | Out-Null
}
}
}
Write-Verbose "Target EXE: $FinalOutputEXE"
# create the sed file used by iexpress
$SED = "$Temp\$Target.sed"
New-Item $SED -ItemType File -Force | Out-Null
# populate the sed with config info
Add-Content $SED "[Version]"
Add-Content $SED "Class=IEXPRESS"
Add-Content $SED "sedVersion=3"
Add-Content $SED "[Options]"
Add-Content $SED "PackagePurpose=InstallApp"
Add-Content $SED "ShowInstallProgramWindow=0"
Add-Content $SED "HideExtractAnimation=1"
Add-Content $SED "UseLongFileName=1"
Add-Content $SED "InsideCompressed=0"
Add-Content $SED "CAB_FixedSize=0"
Add-Content $SED "CAB_ResvCodeSigning=0"
Add-Content $SED "RebootMode=N"
Add-Content $SED "TargetName=%TargetName%"
Add-Content $SED "FriendlyName=%FriendlyName%"
Add-Content $SED "AppLaunched=%AppLaunched%"
Add-Content $SED "PostInstallCmd=%PostInstallCmd%"
Add-Content $SED "SourceFiles=SourceFiles"
Add-Content $SED "[Strings]"
Add-Content $SED "TargetName=$EXE"
Add-Content $SED "FriendlyName=$Target"
# If we've zipped a file, we need to modify things
if ('SelectDirectory', 'SpecifyDirectory' -contains $PSCmdlet.ParameterSetName) {
$IndexOffset = 2
# Create a script to unzip the user's zip
$UnZipScript = New-Item "$Temp\UnZip.ps1" -ItemType File -Force
Add-Content $UnZipScript $UnZipFunction
Add-Content $UnZipScript "UnZip-File `'$SupplementalFiles`'"
# If we're dealing with a zip file, we need to set the primary command to unzip the user's files
Add-Content $SED "AppLaunched=cmd /c PowerShell -NoProfile -ExecutionPolicy Bypass -File `".\UnZip.ps1`""
# After we've staged our files, run the user's script
Add-Content $SED "PostInstallCmd=cmd /c for /f `"skip=1 tokens=1* delims=`" %i in (`'wmic process where `"name=`'$target.exe`'`" get ExecutablePath`') do PowerShell -NoProfile -ExecutionPolicy Bypass -Command Clear-Host; `".\$PSScriptName`" `"%i`" & exit"
Add-Content $SED "FILE0=UnZip.ps1"
Add-Content $SED "FILE1=$PSScriptName"
# Add custom signing certificate to unzip.ps1 to allow for strict PowerShell execution policies.
if ($SignFiles) {
Write-Verbose "Signing unzip.ps1 with certificate thumb print: $($certificatethumb)."
$unzipscript = Get-item "$($Temp)\Unzip.ps1"
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $unzipscript | Out-Null
# Ensure the script is signed successfully with the Thumbprint
if ((((Get-AuthenticodeSignature -FilePath $unzipscript).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing unzip.ps1 failed"
} else {
Write-verbose "unzip.ps1 signing successful."
}
}
} else {
$IndexOffset = 1
Add-Content $SED "AppLaunched=cmd /c for /f `"skip=1 tokens=1* delims=`" %i in (`'wmic process where `"name=`'$target.exe`'`" get ExecutablePath`') do PowerShell -NoProfile -ExecutionPolicy Bypass -Command Clear-Host; `".\$PSScriptName`" `"%i`" & exit"
Add-Content $SED "PostInstallCmd=<None>"
Add-Content $SED "FILE0=$PSScriptName"
}
# Add the ps1 and supplemental files
If ($SupplementalFiles) {
$Index = $IndexOffset
ForEach ($File in $SupplementalFiles) {
$Index++
Add-Content $SED "FILE$Index=$File"
}
}
Add-Content $SED "[SourceFiles]"
Add-Content $SED "SourceFiles0=$Temp"
Add-Content $SED "[SourceFiles0]"
Add-Content $SED "%FILE0%="
if ('SelectDirectory', 'SpecifyDirectory' -contains $PSCmdlet.ParameterSetName) {
# We've already specified this file, so leave it blank here
Add-Content $SED "%FILE1%="
}
# Add the ps1 and supplemental files
If ($SupplementalFiles) {
$Index = $IndexOffset
ForEach ($File in $SupplementalFiles) {
$Index++
Add-Content $SED "%FILE$Index%="
}
}
Write-Verbose "SED file contents: `n$(Get-Content $SED | Out-String)"
# Call IExpress to create exe from the sed we just created (run as admin)
Start-Process $IExpress "/N $SED" -Wait -Verb RunAs
# Sign the final executable with the declared certificate if defined.
if ($SignFiles) {
Write-Verbose "Signing $($EXE) with certificate thumb print: $($certificatethumb)."
try {
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $EXE | Out-Null
} catch {
# Very rarely the system will try to sign the script too fast and will not be able to access the file.
Write-verbose "Signing $($EXE) too fast, waiting 5 seconds and trying again."
start-sleep -seconds 5
Set-AuthenticodeSignature -Certificate $certificateobject -FilePath $EXE | Out-Null
}
# Ensure the script is signed successfully with the Thumbprint
if ((((Get-AuthenticodeSignature -FilePath $EXE).SignerCertificate).Thumbprint) -ne $certificatethumb) {
Write-verbose "Signing $($EXE) failed"
} else {
Write-verbose "$($EXE) signing successful."
}
}
if (!(($OutputDirectory -eq $null) -or ($OutputDirectory -eq ""))) {
Start-Sleep -Milliseconds 250
Write-Verbose "Moving the final executable to $($FinalOutputEXE)"
Move-item $EXE $FinalOutputEXE -Force
}
# Clean up unless user specified not to
if (-not $KeepTempDir) { Remove-Item $Temp -Recurse -Force }
}