-
Notifications
You must be signed in to change notification settings - Fork 515
/
Copy pathAppDeployToolkitMain.ps1
9038 lines (8332 loc) · 377 KB
/
AppDeployToolkitMain.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
<#
.SYNOPSIS
This script contains the functions and logic engine for the Deploy-Application.ps1 script.
.DESCRIPTION
The script can be called directly to dot-source the toolkit functions for testing, but it is usually called by the Deploy-Application.ps1 script.
The script can usually be updated to the latest version without impacting your per-application Deploy-Application scripts.
Please check release notes before upgrading.
.PARAMETER CleanupBlockedApps
Clean up the blocked applications.
This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously.
.PARAMETER ShowBlockedAppDialog
Display a dialog box showing that the application execution is blocked.
This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously.
.PARAMETER ReferringApplication
Title of the referring application that invoked the script externally.
This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously.
.NOTES
The other parameters specified for this script that are not documented in this help section are for use only by functions in this script that call themselves by running this script again asynchronously.
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param
(
## Script Parameters: These parameters are passed to the script when it is called externally from a scheduled task or because of an Image File Execution Options registry setting
[switch]$ShowInstallationPrompt = $false,
[switch]$ShowInstallationRestartPrompt = $false,
[switch]$CleanupBlockedApps = $false,
[switch]$ShowBlockedAppDialog = $false,
[switch]$DisableLogging = $false,
[string]$ReferringApplication = '',
[string]$Message = '',
[string]$MessageAlignment = '',
[string]$ButtonRightText = '',
[string]$ButtonLeftText = '',
[string]$ButtonMiddleText = '',
[string]$Icon = '',
[string]$Timeout = '',
[switch]$ExitOnTimeout = $false,
[boolean]$MinimizeWindows = $false,
[switch]$PersistPrompt = $false,
[int32]$CountdownSeconds,
[int32]$CountdownNoHideSeconds,
[switch]$NoCountdown = $false,
[switch]$RelaunchToolkitAsUser = $false
)
##*=============================================
##* VARIABLE DECLARATION
##*=============================================
#region VariableDeclaration
## Variables: Toolkit Name
[string]$appDeployToolkitName = 'PSAppDeployToolkit'
[string]$appDeployMainScriptFriendlyName = 'App Deploy Toolkit Main'
## Variables: Script Info
[version]$appDeployMainScriptVersion = [version]'3.6.0'
[version]$appDeployMainScriptMinimumConfigVersion = [version]'3.6.0'
[string]$appDeployMainScriptDate = '12/18/2014'
[hashtable]$appDeployMainScriptParameters = $PSBoundParameters
## Variables: Datetime and Culture
[string]$currentTime = (Get-Date -UFormat '%T').ToString()
[string]$currentDate = (Get-Date -UFormat '%d-%m-%Y').ToString()
[timespan]$currentTimeZoneBias = [System.TimeZone]::CurrentTimeZone.GetUtcOffset([datetime]::Now)
[Globalization.CultureInfo]$culture = Get-Culture
[string]$currentLanguage = $culture.TwoLetterISOLanguageName.ToUpper()
## Variables: Environment Variables
[psobject]$envHost = $Host
[string]$envAllUsersProfile = $env:ALLUSERSPROFILE
[string]$envAppData = $env:APPDATA
[string]$envArchitecture = $env:PROCESSOR_ARCHITECTURE
[string]$envCommonProgramFiles = $env:CommonProgramFiles
[string]$envCommonProgramFilesX86 = ${env:CommonProgramFiles(x86)}
[string]$envComputerName = $env:COMPUTERNAME | Where-Object { $_ } | ForEach-Object { $_.ToUpper() }
[string]$envComputerNameFQDN = ([System.Net.Dns]::GetHostEntry('')).HostName
[string]$envHomeDrive = $env:HOMEDRIVE
[string]$envHomePath = $env:HOMEPATH
[string]$envHomeShare = $env:HOMESHARE
[string]$envLocalAppData = $env:LOCALAPPDATA
[string]$envProgramFiles = $env:PROGRAMFILES
[string]$envProgramFilesX86 = ${env:ProgramFiles(x86)}
[string]$envProgramData = $env:PROGRAMDATA
[string]$envPublic = $env:PUBLIC
[string]$envSystemDrive = $env:SYSTEMDRIVE
[string]$envSystemRoot = $env:SYSTEMROOT
[string]$envTemp = $env:TEMP
[string]$envUserName = $env:USERNAME
[string]$envUserProfile = $env:USERPROFILE
[string]$envWinDir = $env:WINDIR
# Handle X86 environment variables so they are never empty
If (-not $envCommonProgramFilesX86) { [string]$envCommonProgramFilesX86 = $env:CommonProgramFiles }
If (-not $envProgramFilesX86) { [string]$envProgramFilesX86 = $env:PROGRAMFILES }
## Variables: Domain Membership
[boolean]$IsMachinePartOfDomain = (Get-WmiObject Win32_ComputerSystem -ErrorAction 'SilentlyContinue').PartOfDomain
[string]$envMachineWorkgroup = ''
[string]$envMachineADDomain = ''
[string]$envLogonServer = ''
[string]$MachineDomainController = ''
If ($IsMachinePartOfDomain) {
[string]$envMachineADDomain = (Get-WmiObject -Class Win32_ComputerSystem -ErrorAction 'SilentlyContinue').Domain | Where-Object { $_ } | ForEach-Object { $_.ToLower() }
Try {
[string]$envLogonServer = $env:LOGONSERVER | Where-Object { (($_) -and (-not $_.Contains('\\MicrosoftAccount'))) } | ForEach-Object { $_.TrimStart('\') } | ForEach-Object { ([System.Net.Dns]::GetHostEntry($_)).HostName }
[string]$MachineDomainController = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().FindDomainController().Name
}
Catch { }
}
Else {
[string]$envMachineWorkgroup = (Get-WmiObject -Class Win32_ComputerSystem -ErrorAction 'SilentlyContinue').Domain | Where-Object { $_ } | ForEach-Object { $_.ToUpper() }
}
[string]$envMachineDNSDomain = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName | Where-Object { $_ } | ForEach-Object { $_.ToLower() }
[string]$envUserDNSDomain = $env:USERDNSDOMAIN | Where-Object { $_ } | ForEach-Object { $_.ToLower() }
[string]$envUserDomain = $env:USERDOMAIN | Where-Object { $_ } | ForEach-Object { $_.ToUpper() }
## Variables: Operating System
[psobject]$envOS = Get-WmiObject -Class Win32_OperatingSystem -ErrorAction 'SilentlyContinue'
[string]$envOSName = $envOS.Caption.Trim()
[string]$envOSServicePack = $envOS.CSDVersion
[version]$envOSVersion = [System.Environment]::OSVersion.Version
[string]$envOSVersionMajor = $envOSVersion.Major
[string]$envOSVersionMinor = $envOSVersion.Minor
[string]$envOSVersionBuild = $envOSVersion.Build
[string]$envOSVersionRevision = $envOSVersion.Revision
[string]$envOSVersion = $envOSVersion.ToString()
# Get the operating system type
[int32]$envOSProductType = $envOS.ProductType
[boolean]$IsServerOS = [boolean]($envOSProductType -eq 3)
[boolean]$IsDomainControllerOS = [boolean]($envOSProductType -eq 2)
[boolean]$IsWorkStationOS = [boolean]($envOSProductType -eq 1)
Switch ($envOSProductType) {
3 { [string]$envOSProductTypeName = 'Server' }
2 { [string]$envOSProductTypeName = 'Domain Controller' }
1 { [string]$envOSProductTypeName = 'Workstation' }
Default { [string]$envOSProductTypeName = 'Unknown' }
}
# Get the OS Architecture
[boolean]$Is64Bit = [boolean]((Get-WmiObject -Class Win32_Processor | Where-Object { $_.DeviceID -eq 'CPU0' } | Select-Object -ExpandProperty AddressWidth) -eq '64')
If ($Is64Bit) { [string]$envOSArchitecture = '64-bit' } Else { [string]$envOSArchitecture = '32-bit' }
## Variables: Current Process Architecture
[boolean]$Is64BitProcess = [boolean]([System.IntPtr]::Size -eq 8)
If ($Is64BitProcess) { [string]$psArchitecture = 'x64' } Else { [string]$psArchitecture = 'x86' }
## Variables: PowerShell And CLR (.NET) Versions
[hashtable]$envPSVersionTable = $PSVersionTable
# PowerShell Version
[version]$envPSVersion = $envPSVersionTable.PSVersion
[string]$envPSVersionMajor = $envPSVersion.Major
[string]$envPSVersionMinor = $envPSVersion.Minor
[string]$envPSVersionBuild = $envPSVersion.Build
[string]$envPSVersionRevision = $envPSVersion.Revision
[string]$envPSVersion = $envPSVersion.ToString()
# CLR (.NET) Version used by PowerShell
[version]$envCLRVersion = $envPSVersionTable.CLRVersion
[string]$envCLRVersionMajor = $envCLRVersion.Major
[string]$envCLRVersionMinor = $envCLRVersion.Minor
[string]$envCLRVersionBuild = $envCLRVersion.Build
[string]$envCLRVersionRevision = $envCLRVersion.Revision
[string]$envCLRVersion = $envCLRVersion.ToString()
## Variables: Permissions/Accounts
[System.Security.Principal.WindowsIdentity]$CurrentProcessToken = [System.Security.Principal.WindowsIdentity]::GetCurrent()
[System.Security.Principal.SecurityIdentifier]$CurrentProcessSID = $CurrentProcessToken.User
[string]$ProcessNTAccount = $CurrentProcessToken.Name
[string]$ProcessNTAccountSID = $CurrentProcessSID.Value
[boolean]$IsAdmin = [boolean]($CurrentProcessToken.Groups -contains [System.Security.Principal.SecurityIdentifier]'S-1-5-32-544')
[boolean]$IsLocalSystemAccount = $CurrentProcessSID.IsWellKnown([System.Security.Principal.WellKnownSidType]'LocalSystemSid')
[boolean]$IsLocalServiceAccount = $CurrentProcessSID.IsWellKnown([System.Security.Principal.WellKnownSidType]'LocalServiceSid')
[boolean]$IsNetworkServiceAccount = $CurrentProcessSID.IsWellKnown([System.Security.Principal.WellKnownSidType]'NetworkServiceSid')
[boolean]$IsServiceAccount = [boolean]($CurrentProcessToken.Groups -contains [System.Security.Principal.SecurityIdentifier]'S-1-5-6')
[boolean]$IsProcessUserInteractive = [System.Environment]::UserInteractive
[string]$LocalSystemNTAccount = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList ([Security.Principal.WellKnownSidType]::'LocalSystemSid', $null)).Translate([System.Security.Principal.NTAccount]).Value
# Check if script is running in session zero
If ($IsLocalSystemAccount -or $IsLocalServiceAccount -or $IsNetworkServiceAccount -or $IsServiceAccount) { $SessionZero = $true } Else { $SessionZero = $false }
## Variables: DPI Scale (property only exists if DPI scaling has been changed on the system at least once)
[int32]$dpiPixels = Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI' -ErrorAction 'SilentlyContinue' | Select-Object -ExpandProperty LogPixels -ErrorAction 'SilentlyContinue'
Switch ($dpiPixels) {
96 { [int32]$dpiScale = 100 }
120 { [int32]$dpiScale = 125 }
144 { [int32]$dpiScale = 150 }
192 { [int32]$dpiScale = 200 }
Default { [int32]$dpiScale = 100 }
}
## Variables: Script Name and Script Paths
[string]$scriptPath = $MyInvocation.MyCommand.Definition
[string]$scriptName = [System.IO.Path]::GetFileNameWithoutExtension($scriptPath)
[string]$scriptFileName = Split-Path -Path $scriptPath -Leaf
[string]$scriptRoot = Split-Path -Path $scriptPath -Parent
[string]$invokingScript = (Get-Variable -Name MyInvocation).Value.ScriptName
# Get the invoking script directory
If ($invokingScript) {
# If this script was invoked by another script
[string]$scriptParentPath = Split-Path -Path $invokingScript -Parent
}
Else {
# If this script was not invoked by another script, fall back to the directory one level above this script
[string]$scriptParentPath = (Get-Item -Path $scriptRoot).Parent.FullName
}
## Variables: App Deploy Script Dependency Files
[string]$appDeployLogoIcon = Join-Path -Path $scriptRoot -ChildPath 'AppDeployToolkitLogo.ico'
[string]$appDeployLogoBanner = Join-Path -Path $scriptRoot -ChildPath 'AppDeployToolkitBanner.png'
[string]$appDeployConfigFile = Join-Path -Path $scriptRoot -ChildPath 'AppDeployToolkitConfig.xml'
# App Deploy Optional Extensions File
[string]$appDeployToolkitDotSourceExtensions = 'AppDeployToolkitExtensions.ps1'
# Check that dependency files are present
If (-not (Test-Path -Path $AppDeployLogoIcon -PathType Leaf)) { Throw 'App Deploy logo icon file not found.' }
If (-not (Test-Path -Path $AppDeployLogoBanner -PathType Leaf)) { Throw 'App Deploy logo banner file not found.' }
If (-not (Test-Path -Path $AppDeployConfigFile -PathType Leaf)) { Throw 'App Deploy XML configuration file not found.' }
## Import variables from XML configuration file
[xml]$xmlConfigFile = Get-Content -Path $AppDeployConfigFile
$xmlConfig = $xmlConfigFile.AppDeployToolkit_Config
# Get Config File Details
$configConfigDetails = $xmlConfig.Config_File
[string]$configConfigVersion = [version]$configConfigDetails.Config_Version
[string]$configConfigDate = $configConfigDetails.Config_Date
# Get Toolkit Options
$xmlToolkitOptions = $xmlConfig.Toolkit_Options
[boolean]$configToolkitRequireAdmin = [boolean]::Parse($xmlToolkitOptions.Toolkit_RequireAdmin)
[boolean]$configToolkitAllowSystemInteraction = [boolean]::Parse($xmlToolkitOptions.Toolkit_AllowSystemInteraction)
[boolean]$configToolkitAllowSystemInteractionFallback = [boolean]::Parse($xmlToolkitOptions.Toolkit_AllowSystemInteractionFallback)
[boolean]$configToolkitAllowSystemInteractionForNonConsoleUser = [boolean]::Parse($xmlToolkitOptions.Toolkit_AllowSystemInteractionForNonConsoleUser)
[string]$configToolkitTempPath = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_TempPath)
[string]$configToolkitRegPath = $xmlToolkitOptions.Toolkit_RegPath
[string]$configToolkitLogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_LogPath)
[boolean]$configToolkitCompressLogs = [boolean]::Parse($xmlToolkitOptions.Toolkit_CompressLogs)
[string]$configToolkitLogStyle = $xmlToolkitOptions.Toolkit_LogStyle
[double]$configToolkitLogMaxSize = $xmlToolkitOptions.Toolkit_LogMaxSize
[boolean]$configToolkitLogWriteToHost = [boolean]::Parse($xmlToolkitOptions.Toolkit_LogWriteToHost)
[boolean]$configToolkitLogDebugMessage = [boolean]::Parse($xmlToolkitOptions.Toolkit_LogDebugMessage)
# Get MSI Options
$xmlConfigMSIOptions = $xmlConfig.MSI_Options
[string]$configMSILoggingOptions = $xmlConfigMSIOptions.MSI_LoggingOptions
[string]$configMSIInstallParams = $xmlConfigMSIOptions.MSI_InstallParams
[string]$configMSISilentParams = $xmlConfigMSIOptions.MSI_SilentParams
[string]$configMSIUninstallParams = $xmlConfigMSIOptions.MSI_UninstallParams
[string]$configMSILogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_LogPath)
[int32]$configMSIMutexWaitTime = $xmlConfigMSIOptions.MSI_MutexWaitTime
# Get UI Options
$xmlConfigUIOptions = $xmlConfig.UI_Options
[boolean]$configShowBalloonNotifications = [boolean]::Parse($xmlConfigUIOptions.ShowBalloonNotifications)
[int32]$configInstallationUITimeout = $xmlConfigUIOptions.InstallationUI_Timeout
[int32]$configInstallationUIExitCode = $xmlConfigUIOptions.InstallationUI_ExitCode
[int32]$configInstallationDeferExitCode = $xmlConfigUIOptions.InstallationDefer_ExitCode
[int32]$configInstallationPersistInterval = $xmlConfigUIOptions.InstallationPrompt_PersistInterval
[int32]$configInstallationRestartPersistInterval = $xmlConfigUIOptions.InstallationRestartPrompt_PersistInterval
# Get Message UI Language Options (default for English if no localization found)
[string]$xmlUIMessageLanguage = "UI_Messages_$currentLanguage"
If (-not ($xmlConfig.$xmlUIMessageLanguage)) { [string]$xmlUIMessageLanguage = 'UI_Messages_EN' }
$xmlUIMessages = $xmlConfig.$xmlUIMessageLanguage
[string]$configDiskSpaceMessage = $xmlUIMessages.DiskSpace_Message
[string]$configBalloonTextStart = $xmlUIMessages.BalloonText_Start
[string]$configBalloonTextComplete = $xmlUIMessages.BalloonText_Complete
[string]$configBalloonTextRestartRequired = $xmlUIMessages.BalloonText_RestartRequired
[string]$configBalloonTextFastRetry = $xmlUIMessages.BalloonText_FastRetry
[string]$configBalloonTextError = $xmlUIMessages.BalloonText_Error
[string]$configProgressMessageInstall = $xmlUIMessages.Progress_MessageInstall
[string]$configProgressMessageUninstall = $xmlUIMessages.Progress_MessageUninstall
[string]$configClosePromptMessage = $xmlUIMessages.ClosePrompt_Message
[string]$configClosePromptButtonClose = $xmlUIMessages.ClosePrompt_ButtonClose
[string]$configClosePromptButtonDefer = $xmlUIMessages.ClosePrompt_ButtonDefer
[string]$configClosePromptButtonContinue = $xmlUIMessages.ClosePrompt_ButtonContinue
[string]$configClosePromptCountdownMessage = $xmlUIMessages.ClosePrompt_CountdownMessage
[string]$configDeferPromptWelcomeMessage = $xmlUIMessages.DeferPrompt_WelcomeMessage
[string]$configDeferPromptExpiryMessage = $xmlUIMessages.DeferPrompt_ExpiryMessage
[string]$configDeferPromptWarningMessage = $xmlUIMessages.DeferPrompt_WarningMessage
[string]$configDeferPromptRemainingDeferrals = $xmlUIMessages.DeferPrompt_RemainingDeferrals
[string]$configDeferPromptDeadline = $xmlUIMessages.DeferPrompt_Deadline
[string]$configBlockExecutionMessage = $xmlUIMessages.BlockExecution_Message
[string]$configDeploymentTypeInstall = $xmlUIMessages.DeploymentType_Install
[string]$configDeploymentTypeUnInstall = $xmlUIMessages.DeploymentType_UnInstall
[string]$configRestartPromptTitle = $xmlUIMessages.RestartPrompt_Title
[string]$configRestartPromptMessage = $xmlUIMessages.RestartPrompt_Message
[string]$configRestartPromptMessageTime = $xmlUIMessages.RestartPrompt_MessageTime
[string]$configRestartPromptMessageRestart = $xmlUIMessages.RestartPrompt_MessageRestart
[string]$configRestartPromptTimeRemaining = $xmlUIMessages.RestartPrompt_TimeRemaining
[string]$configRestartPromptButtonRestartLater = $xmlUIMessages.RestartPrompt_ButtonRestartLater
[string]$configRestartPromptButtonRestartNow = $xmlUIMessages.RestartPrompt_ButtonRestartNow
## Variables: Directories
[string]$dirFiles = Join-Path -Path $scriptParentPath -ChildPath 'Files'
[string]$dirSupportFiles = Join-Path -Path $scriptParentPath -ChildPath 'SupportFiles'
[string]$dirAppDeployTemp = Join-Path -Path $configToolkitTempPath -ChildPath $appDeployToolkitName
## Set up sample variables if Dot Sourcing the script, app details have not been specified, or InstallTitle not passed as parameter to the script
If (-not $appVendor) { [string]$appVendor = 'PS' }
If (-not $appName) { [string]$appName = $appDeployMainScriptFriendlyName }
If (-not $appVersion) { [string]$appVersion = $appDeployMainScriptVersion }
If (-not $appLang) { [string]$appLang = $currentLanguage }
If (-not $appRevision) { [string]$appRevision = '01' }
If (-not $appArch) { [string]$appArch = '' }
[string]$installTitle = "$appVendor $appName $appVersion"
## Sanitize the application details, as they can cause issues in the script
[char[]]$invalidFileNameChars = [System.IO.Path]::GetInvalidFileNamechars()
[string]$appVendor = $appVendor -replace "[$invalidFileNameChars]",'' -replace ' ',''
[string]$appName = $appName -replace "[$invalidFileNameChars]",'' -replace ' ',''
[string]$appVersion = $appVersion -replace "[$invalidFileNameChars]",'' -replace ' ',''
[string]$appArch = $appArch -replace "[$invalidFileNameChars]",'' -replace ' ',''
[string]$appLang = $appLang -replace "[$invalidFileNameChars]",'' -replace ' ',''
[string]$appRevision = $appRevision -replace "[$invalidFileNameChars]",'' -replace ' ',''
## Build the Installation Name
If ($appArch) {
[string]$installName = $appVendor + '_' + $appName + '_' + $appVersion + '_' + $appArch + '_' + $appLang + '_' + $appRevision
}
Else {
[string]$installName = $appVendor + '_' + $appName + '_' + $appVersion + '_' + $appLang + '_' + $appRevision
}
[string]$installName = $installName.Trim('_') -replace '[_]+','_'
## Set the deployment type to "Install" if it has not been specified
If (-not $deploymentType) { [string]$deploymentType = 'Install' }
## Variables: Executables
[string]$exeWusa = 'wusa.exe' # Installs Standalone Windows Updates
[string]$exeMsiexec = 'msiexec.exe' # Installs MSI Installers
[string]$exeSchTasks = "$envWinDir\System32\schtasks.exe" # Manages Scheduled Tasks
## Variables: RegEx Patterns
[string]$MSIProductCodeRegExPattern = '^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$'
## Variables: Registry Keys
# Registry keys for native and WOW64 applications
[string[]]$regKeyApplications = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
If ($is64Bit) {
[string]$regKeyLotusNotes = 'HKLM:SOFTWARE\Wow6432Node\Lotus\Notes'
}
Else {
[string]$regKeyLotusNotes = 'HKLM:SOFTWARE\Lotus\Notes'
}
[string]$regKeyAppExecution = 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options'
[string]$regKeyDeferHistory = "$configToolkitRegPath\$appDeployToolkitName\DeferHistory\$installName"
## COM Objects: Initialize
[__comobject]$Shell = New-Object -ComObject WScript.Shell -ErrorAction 'SilentlyContinue'
[__comobject]$ShellApp = New-Object -ComObject Shell.Application -ErrorAction 'SilentlyContinue'
## Variables: Reset/Remove Variables
[boolean]$msiRebootDetected = $false
[boolean]$BlockExecution = $false
[boolean]$installationStarted = $false
[boolean]$runningTaskSequence = $false
If (Test-Path -Path 'variable:welcomeTimer') { Remove-Variable -Name welcomeTimer -Scope Script}
# Reset the deferral history
If (Test-Path -Path 'variable:deferHistory') { Remove-Variable -Name deferHistory }
If (Test-Path -Path 'variable:deferTimes') { Remove-Variable -Name deferTimes }
If (Test-Path -Path 'variable:deferDays') { Remove-Variable -Name deferDays }
## Variables: Log Files
[string]$logName = $installName + '_' + $appDeployToolkitName + '_' + $deploymentType + '.log'
[string]$logTempFolder = Join-Path -Path $envTemp -ChildPath $installName
If ($configToolkitCompressLogs) {
## If option to compress logs is selected, then log will be created in temp log folder and then copied to actual log folder after being zipped.
# Set log file directory to temp log folder
[string]$logDirectory = $logTempFolder
# The path to the zipped log file in the actual logs folder defined in App Deploy XML config file
[string]$zipFileDate = (Get-Date -Format 'yyyy-MM-dd-hh-mm-ss').ToString()
[string]$zipFileName = Join-Path -Path $configToolkitLogDir -ChildPath ($installName + '_' + $deploymentType + '_' + $zipFileDate + '.zip')
# If the temp log folder already exists from a previous ZIP operation, then delete all files in it to avoid issues
If (Test-Path -Path $logTempFolder -PathType Container -ErrorAction 'SilentlyContinue') {
Remove-Item -Path $logTempFolder -Recurse -Force -ErrorAction 'SilentlyContinue' | Out-Null
}
}
Else {
## Path to log directory defined in AppDeploy XML config file
[string]$logDirectory = $configToolkitLogDir
}
#endregion
##*=============================================
##* END VARIABLE DECLARATION
##*=============================================
##*=============================================
##* FUNCTION LISTINGS
##*=============================================
#region FunctionListings
#region Function Write-FunctionHeaderOrFooter
Function Write-FunctionHeaderOrFooter {
<#
.SYNOPSIS
Write the function header or footer to the log upon first entering or exiting a function.
.DESCRIPTION
Write the "Function Start" message, the bound parameters the function was invoked with, or the "Function End" message when entering or exiting a function.
Messages are debug messages so will only be logged if LogDebugMessage option is enabled in XML config file.
.PARAMETER CmdletName
The name of the function this function is invoked from.
.PARAMETER CmdletBoundParameters
The bound parameters of the function this function is invoked from.
.PARAMETER Header
Write the function header.
.PARAMETER Footer
Write the function footer.
.EXAMPLE
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
.EXAMPLE
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
.NOTES
This is an internal script function and should typically not be called directly.
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[ValidateNotNullorEmpty()]
[string]$CmdletName,
[Parameter(Mandatory=$true,ParameterSetName='Header')]
[AllowEmptyCollection()]
[hashtable]$CmdletBoundParameters,
[Parameter(Mandatory=$true,ParameterSetName='Header')]
[switch]$Header,
[Parameter(Mandatory=$true,ParameterSetName='Footer')]
[switch]$Footer
)
If ($Header) {
Write-Log -Message 'Function Start' -Source ${CmdletName} -DebugMessage
## Get the parameters that the calling function was invoked with
[string]$CmdletBoundParameters = $CmdletBoundParameters | Format-Table -Property @{ Label = 'Parameter'; Expression = { "[-$($_.Key)]" } }, @{ Label = 'Value'; Expression = { $_.Value }; Alignment = 'Left' } -AutoSize -Wrap | Out-String
If ($CmdletBoundParameters) {
Write-Log -Message "Function invoked with bound parameter(s): `n$CmdletBoundParameters" -Source ${CmdletName} -DebugMessage
}
Else {
Write-Log -Message 'Function invoked without any bound parameters' -Source ${CmdletName} -DebugMessage
}
}
ElseIf ($Footer) {
Write-Log -Message 'Function End' -Source ${CmdletName} -DebugMessage
}
}
#endregion
#region Function Write-Log
Function Write-Log {
<#
.SYNOPSIS
Write messages to a log file in CMTrace.exe compatible format or Legacy text file format.
.DESCRIPTION
Write messages to a log file in CMTrace.exe compatible format or Legacy text file format and optionally display in the console.
.PARAMETER Message
The message to write to the log file or output to the console.
.PARAMETER Severity
Defines message type. When writing to console or CMTrace.exe log format, it allows highlighting of message type.
Options: 1 = Information (default), 2 = Warning (highlighted in yellow), 3 = Error (highlighted in red)
.PARAMETER Source
The source of the message being logged.
.PARAMETER ScriptSection
The heading for the portion of the script that is being executed. Default is: $script:installPhase.
.PARAMETER LogType
Choose whether to write a CMTrace.exe compatible log file or a Legacy text log file.
.PARAMETER LogFileDirectory
Set the directory where the log file will be saved.
.PARAMETER LogFileName
Set the name of the log file.
.PARAMETER MaxLogFileSizeMB
Maximum file size limit for log file in megabytes (MB). Default is 10 MB.
.PARAMETER WriteHost
Write the log message to the console.
.PARAMETER ContinueOnError
Suppress writing log message to console on failure to write message to log file.
.PARAMETER PassThru
Return the message that was passed to the function
.PARAMETER DebugMessage
Specifies that the message is a debug message. Debug messages only get logged if -LogDebugMessage is set to $true.
.PARAMETER LogDebugMessage
Debug messages only get logged if this parameter is set to $true in the config XML file.
.EXAMPLE
Write-Log -Message "Installing patch MS15-031" -Source 'Add-Patch' -LogType 'CMTrace'
.EXAMPLE
Write-Log -Message "Script is running on Windows 8" -Source 'Test-ValidOS' -LogType 'Legacy'
.NOTES
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[AllowEmptyCollection()]
[string[]]$Message,
[Parameter(Mandatory=$false,Position=1)]
[ValidateRange(1,3)]
[int16]$Severity = 1,
[Parameter(Mandatory=$false,Position=2)]
[ValidateNotNull()]
[string]$Source = '',
[Parameter(Mandatory=$false,Position=3)]
[ValidateNotNullorEmpty()]
[string]$ScriptSection = $script:installPhase,
[Parameter(Mandatory=$false,Position=4)]
[ValidateSet('CMTrace','Legacy')]
[string]$LogType = $configToolkitLogStyle,
[Parameter(Mandatory=$false,Position=5)]
[ValidateNotNullorEmpty()]
[string]$LogFileDirectory = $logDirectory,
[Parameter(Mandatory=$false,Position=6)]
[ValidateNotNullorEmpty()]
[string]$LogFileName = $logName,
[Parameter(Mandatory=$false,Position=7)]
[ValidateNotNullorEmpty()]
[decimal]$MaxLogFileSizeMB = $configToolkitLogMaxSize,
[Parameter(Mandatory=$false,Position=8)]
[ValidateNotNullorEmpty()]
[boolean]$WriteHost = $configToolkitLogWriteToHost,
[Parameter(Mandatory=$false,Position=9)]
[ValidateNotNullorEmpty()]
[boolean]$ContinueOnError = $true,
[Parameter(Mandatory=$false,Position=10)]
[switch]$PassThru = $false,
[Parameter(Mandatory=$false,Position=11)]
[switch]$DebugMessage = $false,
[Parameter(Mandatory=$false,Position=12)]
[boolean]$LogDebugMessage = $configToolkitLogDebugMessage,
[Parameter(Mandatory=$false,Position=13)]
[switch]$DisableOnRelaunchToolkitAsUser = $false
)
Begin {
## Get the name of this function
[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
## Initialize Variables
[string]$LogTime = (Get-Date -Format HH:mm:ss.fff).ToString()
[string]$LogDate = (Get-Date -Format MM-dd-yyyy).ToString()
If (-not (Test-Path -Path 'variable:LogTimeZoneBias')) { [int32]$script:LogTimeZoneBias = [System.TimeZone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes }
[string]$LogTimePlusBias = $LogTime + $script:LogTimeZoneBias
[boolean]$ExitLoggingFunction = $false
## Exit function if logging was disabled because toolkit was dot sourced again so that a command could be executed in the user context
If ($DisableOnRelaunchToolkitAsUser -and $RelaunchToolkitAsUser) { [boolean]$ExitLoggingFunction = $true; Return }
## Exit function if it is a debug message and logging debug messages is not enabled in the config XML file
If (($DebugMessage) -and (-not $LogDebugMessage)) { [boolean]$ExitLoggingFunction = $true; Return }
## Create the directory where the log file will be saved
If (-not (Test-Path -Path $LogFileDirectory -PathType Container)) {
Try {
New-Item -Path $LogFileDirectory -Type 'Directory' -Force -ErrorAction 'Stop' | Out-Null
}
Catch {
[boolean]$ExitLoggingFunction = $true
# If error creating directory, write message to console
If (-not $ContinueOnError) {
Write-Host "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the log directory [$LogFileDirectory]. `n$(Resolve-Error)" -ForegroundColor 'Red'
}
Return
}
}
## Get the file name of the source script
If ($script:MyInvocation.Value.ScriptName) { [string]$ScriptSource = Split-Path -Path $script:MyInvocation.Value.ScriptName -Leaf } Else { [string]$ScriptSource = Split-Path -Path $script:MyInvocation.MyCommand.Definition -Leaf }
## Check if the script section is defined
[boolean]$ScriptSectionDefined = [boolean](-not [string]::IsNullOrEmpty($ScriptSection))
## Initialize $DisableLogging variable to avoid error if 'Set-StrictMode' is set
If (-not (Test-Path -Path 'variable:DisableLogging')) { $DisableLogging = $false }
## Create script block for generating CMTrace.exe compatible log entry
[scriptblock]$CMTraceLogString = {
Param (
[string]$lMessage,
[string]$lSource,
[int16]$lSeverity
)
"<![LOG[$lMessage]LOG]!>" + "<time=`"$LogTimePlusBias`" " + "date=`"$LogDate`" " + "component=`"$lSource`" " + "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " + "type=`"$lSeverity`" " + "thread=`"$PID`" " + "file=`"$ScriptSource`">"
}
## Create script block for writing log entry to the console
[scriptblock]$WriteLogLineToHost = {
Param (
[string]$lTextLogLine,
[int16]$lSeverity
)
If ($WriteHost) {
# Only output using color options if running in a host which supports colors.
If ($Host.UI.RawUI.ForegroundColor) {
Switch ($lSeverity) {
3 { Write-Host $lTextLogLine -ForegroundColor 'Red' -BackgroundColor 'Black' }
2 { Write-Host $lTextLogLine -ForegroundColor 'Yellow' -BackgroundColor 'Black' }
1 { Write-Host $lTextLogLine }
}
}
# If executing "powershell.exe -File <filename>.ps1 > log.txt", then all the Write-Host calls are converted to Write-Output calls so that they are included in the text log.
Else {
Write-Output $lTextLogLine
}
}
}
# Assemble the fully qualified path to the log file
[string]$LogFilePath = Join-Path -Path $LogFileDirectory -ChildPath $LogFileName
}
Process {
## Exit function if logging is disabled or if the log directory was not successfully created in 'Begin' block.
If ($ExitLoggingFunction) { Return }
ForEach ($Msg in $Message) {
## If the message is not $null or empty, create the log entry for the different logging methods
[string]$CMTraceMsg = ''
[string]$ConsoleLogLine = ''
[string]$LegacyTextLogLine = ''
If ($Msg) {
# Create the CMTrace log message
If ($ScriptSectionDefined) { [string]$CMTraceMsg = "[$ScriptSection] :: $Msg" }
# Create a Console and Legacy "text" log entry
[string]$LegacyMsg = "[$LogDate $LogTime]"
If ($ScriptSectionDefined) { [string]$LegacyMsg += " [$ScriptSection]" }
If ($Source) {
[string]$ConsoleLogLine = "$LegacyMsg [$Source] :: $Msg"
Switch ($Severity) {
3 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Error] :: $Msg" }
2 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Warning] :: $Msg" }
1 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Info] :: $Msg" }
}
}
Else {
[string]$ConsoleLogLine = "$LegacyMsg :: $Msg"
Switch ($Severity) {
3 { [string]$LegacyTextLogLine = "$LegacyMsg [Error] :: $Msg" }
2 { [string]$LegacyTextLogLine = "$LegacyMsg [Warning] :: $Msg" }
1 { [string]$LegacyTextLogLine = "$LegacyMsg [Info] :: $Msg" }
}
}
}
## Execute script block to create the CMTrace.exe compatible log entry
[string]$CMTraceLogLine = & $CMTraceLogString -lMessage $CMTraceMsg -lSource $Source -lSeverity $Severity
## Choose which log type to write to file
If ($LogType -ieq 'CMTrace') {
[string]$LogLine = $CMTraceLogLine
}
Else {
[string]$LogLine = $LegacyTextLogLine
}
## Write the log entry to the log file if logging is not currently disabled
If (-not $DisableLogging) {
Try {
$LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Stop'
}
Catch {
If (-not $ContinueOnError) {
Write-Host "[$LogDate $LogTime] [$ScriptSection] [${CmdletName}] :: Failed to write message [$Msg] to the log file [$LogFilePath]. `n$(Resolve-Error)" -ForegroundColor 'Red'
}
}
}
## Execute script block to write the log entry to the console if $WriteHost is $true
& $WriteLogLineToHost -lTextLogLine $ConsoleLogLine -lSeverity $Severity
}
}
End {
## Archive log file if size is greater than $MaxLogFileSizeMB and $MaxLogFileSizeMB > 0
Try {
If (-not $ExitLoggingFunction) {
[System.IO.FileInfo]$LogFile = Get-ChildItem -Path $LogFilePath -ErrorAction 'Stop'
[decimal]$LogFileSizeMB = $LogFile.Length/1MB
If (($LogFileSizeMB -gt $MaxLogFileSizeMB) -and ($MaxLogFileSizeMB -gt 0)) {
## Change the file extension to "lo_"
[string]$ArchivedOutLogFile = [System.IO.Path]::ChangeExtension($LogFilePath, 'lo_')
[hashtable]$ArchiveLogParams = @{ ScriptSection = $ScriptSection; Source = ${CmdletName}; Severity = 2; LogFileDirectory = $LogFileDirectory; LogFileName = $LogFilePath; LogType = $LogType; MaxLogFileSizeMB = 0; WriteHost = $WriteHost; ContinueOnError = $ContinueOnError; PassThru = $false }
## Log message about archiving the log file
$ArchiveLogMessage = "Maximum log file size [$MaxLogFileSizeMB MB] reached. Rename log file to [$ArchivedOutLogFile]."
Write-Log -Message $ArchiveLogMessage @ArchiveLogParams
## Archive existing log file from <filename>.log to <filename>.lo_. Overwrites any existing <filename>.lo_ file. This is the same method SCCM uses for log files.
Move-Item -Path $LogFilePath -Destination $ArchivedOutLogFile -Force -ErrorAction 'Stop'
## Start new log file and Log message about archiving the old log file
$NewLogMessage = "Previous log file was renamed to [$ArchivedOutLogFile] because maximum log file size of [$MaxLogFileSizeMB MB] was reached."
Write-Log -Message $NewLogMessage @ArchiveLogParams
}
}
}
Catch {
## If renaming of file fails, script will continue writing to log file even if size goes over the max file size
}
Finally {
If ($PassThru) { Write-Output $Message }
}
}
}
#endregion
#region Function Exit-Script
Function Exit-Script {
<#
.SYNOPSIS
Exit the script, perform cleanup actions, and pass an exit code to the parent process.
.DESCRIPTION
Always use when exiting the script to ensure cleanup actions are performed.
.PARAMETER ExitCode
The exit code to be passed from the script to the parent process, e.g. SCCM
.EXAMPLE
Exit-Script -ExitCode 0
.EXAMPLE
Exit-Script -ExitCode 1618
.NOTES
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
[int32]$ExitCode = 0
)
## Get the name of this function
[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
## Stop the Close Program Dialog if running
If ($formCloseApps) { $formCloseApps.Close }
## Close the Installation Progress Dialog if running
If (Test-Path -Path "$dirAppDeployTemp\StatusMsgFrom_ShowInstallProgress.xml" -PathType 'Leaf') {
[string]$StatusMessage = '_CloseRunspace'
$StatusMessage | Export-Clixml -Path "$dirAppDeployTemp\StatusMsgFrom_ShowInstallProgress.xml" -Force
}
Start-Sleep -Seconds 5
Close-InstallationProgress
## If block execution variable is true, call the function to unblock execution
If (($BlockExecution) -and (-not $RelaunchToolkitAsUser)) { Unblock-AppExecution }
## If Terminal Server mode was set, turn it off
If (($terminalServerMode) -and (-not $RelaunchToolkitAsUser)) { Disable-TerminalServerInstallMode }
## Determine action based on exit code
Switch ($exitCode) {
$configInstallationUIExitCode { $installSuccess = $false }
$configInstallationDeferExitCode { $installSuccess = $false }
3010 { $installSuccess = $true }
1641 { $installSuccess = $true }
0 { $installSuccess = $true }
Default { $installSuccess = $false }
}
## Determine if baloon notification should be shown
If ($deployModeSilent) { [boolean]$configShowBalloonNotifications = $false }
If ($installSuccess) {
If (Test-Path -Path $regKeyDeferHistory -ErrorAction 'SilentlyContinue') {
Write-Log -Message 'Remove deferral history...' -Source ${CmdletName}
Remove-RegistryKey -Key $regKeyDeferHistory
}
[string]$balloonText = "$deploymentTypeName $configBalloonTextComplete"
## Handle reboot prompts on successful script completion
If (($msiRebootDetected) -and ($AllowRebootPassThru)) {
Write-Log -Message 'A restart has been flagged as required.' -Source ${CmdletName}
[string]$balloonText = "$deploymentTypeName $configBalloonTextRestartRequired"
[int32]$exitCode = 3010
}
Else {
[int32]$exitCode = 0
}
Write-Log -Message "$installName $deploymentTypeName completed with exit code [$exitcode]." -Source ${CmdletName}
If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Info' -BalloonTipText $balloonText }
}
ElseIf (-not $installSuccess) {
Write-Log -Message "$installName $deploymentTypeName completed with exit code [$exitcode]." -Source ${CmdletName}
If (($exitCode -eq $configInstallationUIExitCode) -or ($exitCode -eq $configInstallationDeferExitCode)) {
[string]$balloonText = "$deploymentTypeName $configBalloonTextFastRetry"
If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Warning' -BalloonTipText $balloonText }
}
Else {
[string]$balloonText = "$deploymentTypeName $configBalloonTextError"
If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Error' -BalloonTipText $balloonText }
}
}
[string]$LogDash = '-' * 79
Write-Log -Message $LogDash -Source ${CmdletName}
## Compress the log files and remove the temporary folder
If (($configToolkitCompressLogs) -and (-not $RelaunchToolkitAsUser)) {
Try {
# Add the file header for zip files to a file and create a 0 byte .zip file
Set-Content -Path $zipFileName -Value ('PK' + [char]5 + [char]6 + ("$([char]0)" * 18)) -ErrorAction 'Stop'
$zipFile = $shellApp.NameSpace($zipFileName)
ForEach ($file in (Get-ChildItem -Path $logTempFolder -ErrorAction 'Stop')) {
Write-Log -Message "Compress log file [$($file.Name)] to [$zipFileName]..." -Source ${CmdletName}
$zipFile.CopyHere($file.FullName)
Start-Sleep -Milliseconds 500
}
If (Test-Path -Path $logTempFolder -PathType Container -ErrorAction 'Stop') {
Remove-Item -Path $logTempFolder -Recurse -Force -ErrorAction 'Stop' | Out-Null
}
}
Catch {
Write-Log -Message "Failed to compress the log file(s). `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
}
}
## Exit the script, returning the exit code to SCCM
Exit $exitCode
}
#endregion
#region Function Resolve-Error
Function Resolve-Error {
<#
.SYNOPSIS
Enumerate error record details.
.DESCRIPTION
Enumerate an error record, or a collection of error record, properties. By default, the details for the last error will be enumerated.
.PARAMETER ErrorRecord
The error record to resolve. The default error record is the latest one: $global:Error[0]. This parameter will also accept an array of error records.
.PARAMETER Property
The list of properties to display from the error record. Use "*" to display all properties.
Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException
.PARAMETER GetErrorRecord
Get error record details as represented by $_.
.PARAMETER GetErrorInvocation
Get error record invocation information as represented by $_.InvocationInfo.
.PARAMETER GetErrorException
Get error record exception details as represented by $_.Exception.
.PARAMETER GetErrorInnerException
Get error record inner exception details as represented by $_.Exception.InnerException. Will retrieve all inner exceptions if there is more than one.
.EXAMPLE
Resolve-Error
.EXAMPLE
Resolve-Error -Property *
.EXAMPLE
Resolve-Error -Property InnerException
.EXAMPLE
Resolve-Error -GetErrorInvocation:$false
.NOTES
.LINK
http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$false,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[AllowEmptyCollection()]
[array]$ErrorRecord,
[Parameter(Mandatory=$false,Position=1)]
[ValidateNotNullorEmpty()]
[string[]]$Property = ('Message','InnerException','FullyQualifiedErrorId','ScriptStackTrace','PositionMessage'),
[Parameter(Mandatory=$false,Position=2)]
[switch]$GetErrorRecord = $true,
[Parameter(Mandatory=$false,Position=3)]
[switch]$GetErrorInvocation = $true,
[Parameter(Mandatory=$false,Position=4)]
[switch]$GetErrorException = $true,
[Parameter(Mandatory=$false,Position=5)]
[switch]$GetErrorInnerException = $true
)
Begin {
## If function was called without specifying an error record, then choose the latest error that occurred
If (-not $ErrorRecord) {
If ($global:Error.Count -eq 0) {
#Write-Warning -Message "The `$Error collection is empty"
Return
}
Else {
[array]$ErrorRecord = $global:Error[0]
}
}
## Allows selecting and filtering the properties on the error object if they exist
[scriptblock]$SelectProperty = {
Param (
[Parameter(Mandatory=$true)]
[ValidateNotNullorEmpty()]
$InputObject,
[Parameter(Mandatory=$true)]
[ValidateNotNullorEmpty()]
[string[]]$Property
)
[string[]]$ObjectProperty = $InputObject | Get-Member -MemberType *Property | Select-Object -ExpandProperty Name
ForEach ($Prop in $Property) {
If ($Prop -eq '*') {
[string[]]$PropertySelection = $ObjectProperty
Break
}
ElseIf ($ObjectProperty -contains $Prop) {
[string[]]$PropertySelection += $Prop
}
}
Write-Output $PropertySelection
}
# Initialize variables to avoid error if 'Set-StrictMode' is set
$LogErrorRecordMsg = $null
$LogErrorInvocationMsg = $null
$LogErrorExceptionMsg = $null
$LogErrorMessageTmp = $null
$LogInnerMessage = $null
}
Process {
If (-not $ErrorRecord) { Return }
ForEach ($ErrRecord in $ErrorRecord) {
## Capture Error Record
If ($GetErrorRecord) {
[string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord -Property $Property
$LogErrorRecordMsg = $ErrRecord | Select-Object -Property $SelectedProperties
}
## Error Invocation Information
If ($GetErrorInvocation) {
If ($ErrRecord.InvocationInfo) {
[string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.InvocationInfo -Property $Property
$LogErrorInvocationMsg = $ErrRecord.InvocationInfo | Select-Object -Property $SelectedProperties
}
}
## Capture Error Exception
If ($GetErrorException) {
If ($ErrRecord.Exception) {
[string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.Exception -Property $Property
$LogErrorExceptionMsg = $ErrRecord.Exception | Select-Object -Property $SelectedProperties
}
}
## Display properties in the correct order
If ($Property -eq '*') {
# If all properties were chosen for display, then arrange them in the order the error object displays them by default.
If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
}
Else {
# Display selected properties in our custom order
If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
}
If ($LogErrorMessageTmp) {
$LogErrorMessage = 'Error Record:'
$LogErrorMessage += "`n-------------"
$LogErrorMsg = $LogErrorMessageTmp | Format-List | Out-String
$LogErrorMessage += $LogErrorMsg
}
## Capture Error Inner Exception(s)
If ($GetErrorInnerException) {
If ($ErrRecord.Exception -and $ErrRecord.Exception.InnerException) {
$LogInnerMessage = 'Error Inner Exception(s):'
$LogInnerMessage += "`n-------------------------"
$ErrorInnerException = $ErrRecord.Exception.InnerException
$Count = 0
While ($ErrorInnerException) {
[string]$InnerExceptionSeperator = '~' * 40
[string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrorInnerException -Property $Property
$LogErrorInnerExceptionMsg = $ErrorInnerException | Select-Object -Property $SelectedProperties | Format-List | Out-String
If ($Count -gt 0) { $LogInnerMessage += $InnerExceptionSeperator }
$LogInnerMessage += $LogErrorInnerExceptionMsg
$Count++
$ErrorInnerException = $ErrorInnerException.InnerException
}
}
}
If ($LogErrorMessage) { $Output = $LogErrorMessage }
If ($LogInnerMessage) { $Output += $LogInnerMessage }
Write-Output $Output
If (Test-Path -Path 'variable:Output') { Clear-Variable -Name Output }
If (Test-Path -Path 'variable:LogErrorMessage') { Clear-Variable -Name LogErrorMessage }
If (Test-Path -Path 'variable:LogInnerMessage') { Clear-Variable -Name LogInnerMessage }
If (Test-Path -Path 'variable:LogErrorMessageTmp') { Clear-Variable -Name LogErrorMessageTmp }
}
}
End {
}
}
#endregion