-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDetection.ps1
More file actions
1568 lines (1384 loc) · 136 KB
/
Detection.ps1
File metadata and controls
1568 lines (1384 loc) · 136 KB
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
Proaction Remediation script for staging and controlling required files for feature updates of Windows.
.DESCRIPTION
This is the detection script for a Proactive Remediation in Endpoint Analytics used control the feature update automation aspects around Windows setup.
.EXAMPLE
.\Detection.ps1
.NOTES
FileName: Detection.ps1
Author: Nickolaj Andersen
Contact: @NickolajA
Created: 2024-08-26
Updated: 2024-08-26
Version history:
1.0.0 - (2024-08-26) Script created
#>
Begin {
# Define the proactive remediation name
$ProactiveRemediationName = "FeatureUpdateController"
# Define if any modules must be present on the device for this proactive remediation to execute properly
# Set to $null if no modules are to be installed
$Modules = @("Az.Storage", "Az.Resources")
# Enable TLS 1.2 support for downloading modules from PSGallery
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Install required modules for script execution
if ($Modules -ne $null) {
foreach ($Module in $Modules) {
try {
$CurrentModule = Get-InstalledModule -Name $Module -ErrorAction "Stop" -Verbose:$false
if ($CurrentModule -ne $null) {
$LatestModuleVersion = (Find-Module -Name $Module -ErrorAction "Stop" -Verbose:$false).Version
if ($LatestModuleVersion -gt $CurrentModule.Version) {
$UpdateModuleInvocation = Update-Module -Name $Module -Force -ErrorAction "Stop" -Confirm:$false -Verbose:$false
}
}
}
catch [System.Exception] {
try {
# Install NuGet package provider
$PackageProvider = Install-PackageProvider -Name "NuGet" -Force -Verbose:$false
# Install current missing module
Install-Module -Name $Module -Force -ErrorAction "Stop" -Confirm:$false -Verbose:$false
}
catch [System.Exception] {
Write-Warning -Message "An error occurred while attempting to install $($Module) module. Error message: $($_.Exception.Message)"
}
}
}
}
}
Process {
# Functions
function Write-LogEntry {
param (
[parameter(Mandatory = $true, HelpMessage = "Value added to the log file.")]
[ValidateNotNullOrEmpty()]
[string]$Value,
[parameter(Mandatory = $true, HelpMessage = "Severity for the log entry. 1 for Informational, 2 for Warning and 3 for Error.")]
[ValidateNotNullOrEmpty()]
[ValidateSet("1", "2", "3")]
[string]$Severity,
[parameter(Mandatory = $false, HelpMessage = "Name of the log file that the entry will written to.")]
[ValidateNotNullOrEmpty()]
[string]$FileName = "$($ProactiveRemediationName).log"
)
# Check if the script is running as SYSTEM, else use the user's temp folder for the log file location
if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem -eq $true) {
$LogFilePath = Join-Path -Path (Join-Path -Path $env:ProgramData -ChildPath "Microsoft\IntuneManagementExtension\Logs") -ChildPath $FileName
}
else {
$LogFilePath = Join-Path -Path (Join-Path -Path $env:TEMP -ChildPath "RemediationScript\Logs") -ChildPath $FileName
}
# Create log folder path if it does not exist
try {
$LogFolderPath = Split-Path -Path $LogFilePath -Parent
if (-not(Test-Path -Path $LogFolderPath)) {
New-Item -ItemType "Directory" -Path $LogFolderPath -Force -ErrorAction "Stop" | Out-Null
}
}
catch [System.Exception] {
Write-Warning -Message "An error occurred while attempting to create the log folder path. Error message at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)"
}
# Construct time stamp for log entry
$Time = -join @((Get-Date -Format "HH:mm:ss.fff"), "+", (Get-WmiObject -Class Win32_TimeZone | Select-Object -ExpandProperty Bias))
# Construct date for log entry
$Date = (Get-Date -Format "MM-dd-yyyy")
# Construct context for log entry
$Context = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
# Construct final log entry
$LogText = "<![LOG[$($Value)]LOG]!><time=""$($Time)"" date=""$($Date)"" component=""$($ProactiveRemediationName)"" context=""$($Context)"" type=""$($Severity)"" thread=""$($PID)"" file="""">"
# Add value to log file
try {
Out-File -InputObject $LogText -Append -NoClobber -Encoding Default -FilePath $LogFilePath -ErrorAction Stop
}
catch [System.Exception] {
Write-Warning -Message "Unable to append log entry $($ProactiveRemediationName).log file. Error message at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)"
}
}
function Out-SetupConfigIniFile {
param(
[parameter(Mandatory = $true, HelpMessage = "Path to the INI file to be created.")]
[ValidateNotNullOrEmpty()]
[string]$Path,
[parameter(Mandatory = $true, HelpMessage = "Data to be written to the INI file.")]
[ValidateNotNullOrEmpty()]
[System.Collections.Specialized.OrderedDictionary]$Value
)
Process {
# Add the default section header
$Data = "[SetupConfig]"
# Loop through each key and value in the ordered dictionary and insert into the data string
foreach ($DataKey in $Value.Keys) {
# Add the key and value to the data string
$Data += "`r`n$($DataKey)=$($Value[$DataKey])"
}
try {
# Write the data to the INI file
Out-File -FilePath $Path -InputObject $Data -Encoding "ascii" -ErrorAction "Stop"
}
catch [System.Exception] {
throw "$($MyInvocation.MyCommand): Error message: $($_.Exception.Message)"
}
}
}
# Check if the script is running as SYSTEM, else declare the user's temp folder for the log file location used to rotation check
if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem -eq $true) {
$LogFilePath = Join-Path -Path (Join-Path -Path $env:ProgramData -ChildPath "Microsoft\IntuneManagementExtension\Logs") -ChildPath $FileName
}
else {
$LogFilePath = Join-Path -Path (Join-Path -Path $env:TEMP -ChildPath "RemediationScript\Logs") -ChildPath $FileName
}
# Check if the log file is larger than 3MB, if true, rotate the log file
$LogFile = Join-Path -Path $LogFilePath -ChildPath "$($ProactiveRemediationName).log"
if (Test-Path -Path $LogFile) {
# Get log file size
$LogFileSize = (Get-Item -Path $LogFile).Length
# Rotate log file if it is larger than 3MB
if ($LogFileSize -gt 3145728) {
$LogFileName = [System.IO.Path]::GetFileNameWithoutExtension($LogFile)
$LogFileExtension = [System.IO.Path]::GetExtension($LogFile)
$LogFileDateTime = (Get-Date).ToString("yyyyMMddHHmmss")
$LogFileNewPath = Join-Path -Path $LogFilePath -ChildPath "$($LogFileName)_$($LogFileDateTime)$($LogFileExtension)"
try {
# Copy existing log file to new path, remove the existing log file and create a new empty log file
Copy-Item -Path $LogFile -Destination $LogFileNewPath -Force -ErrorAction "Stop" | Out-Null
Remove-Item -Path $LogFile -ErrorAction "Stop" | Out-Null
New-Item -Path $LogFile -ItemType "File" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
Write-Warning -Message "An error occurred while attempting to rotate the log file. Error message: $($_.Exception.Message)"
}
# Keep only the last 2 rotated log files, remove the rest
$LogFiles = Get-ChildItem -Path $LogFilePath | Sort-Object -Property CreationTime -Descending
if ($LogFiles.Count -gt 2) {
$LogFiles | Select-Object -Skip 2 | Remove-Item -Force -ErrorAction "Stop" | Out-Null
}
}
}
# Initial logging details for detection script
Write-LogEntry -Value "[$($ProactiveRemediationName)-Detection] - Initializing" -Severity 1
# Declare variable for company name
$CompanyName = "<company_name>"
# Declare manifest and Azure storage account container variables
$StorageAccountName = "<storage_account_name>"
$StorageAccountContainer = "<storage_account_container_name>"
$ManifestFileName = "manifest.json"
# Declare registry root path for version control of each modules (scripts to be executed)
$RegistryRootKey = "HKLM:\SOFTWARE\$($CompanyName)\FeatureUpdateController"
# Declare the feature update controller root directory in ProgramData
$ProgramDataFeatureUpdateControllerRootPath = Join-Path -Path $env:SystemDrive -ChildPath "ProgramData\$($CompanyName)\FeatureUpdateController"
# Declare temporary download destinations
$TemporaryDownloadPath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath "Temp"
# Declare directory path for modules to be installed
$ModulesDirectoryPath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath "Modules"
# Declare directory path for modules to be installed
$CustomActionScriptsDirectoryPath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath "CustomActions"
# Declare variables for Windows setup files and paths
$WindowsSetupConfigFilePath = Join-Path -Path $env:SystemDrive -ChildPath "Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini"
# Declare variable for prerequisities to continue script operation
$ScriptOperationPrerequisites = $true
# Declare error message variable
$ErrorMessage = $null
# Output script paths
Write-LogEntry -Value "- These environment paths and registry locations will be used" -Severity 1
Write-LogEntry -Value "- Company name: $($CompanyName)" -Severity 1
Write-LogEntry -Value "- Feature Update Controller registry root key: $($RegistryRootKey)" -Severity 1
Write-LogEntry -Value "- Feature Update Controller directory root path: $($ProgramDataFeatureUpdateControllerRootPath)" -Severity 1
Write-LogEntry -Value "- Temporary download path: $($TemporaryDownloadPath)" -Severity 1
Write-LogEntry -Value "- Windows setup config file path: $($WindowsSetupConfigFilePath)" -Severity 1
# Test if registry key exists
if (-not(Test-Path -Path $RegistryRootKey)) {
Write-LogEntry -Value "- Registry key does not exist, creating: $($RegistryRootKey)" -Severity 1
try {
# Create registry key
New-Item -Path $RegistryRootKey -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Test if feature update controller root path exists
if (-not(Test-Path -Path $ProgramDataFeatureUpdateControllerRootPath)) {
Write-LogEntry -Value "- Feature update controller root path does not exist, creating: $($ProgramDataFeatureUpdateControllerRootPath)" -Severity 1
try {
# Create feature update controller root path
New-Item -Path $ProgramDataFeatureUpdateControllerRootPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create feature update controller root path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Test if temporary download path exists
if (-not(Test-Path -Path $TemporaryDownloadPath)) {
Write-LogEntry -Value "- Temporary download path does not exist, creating: $($TemporaryDownloadPath)" -Severity 1
try {
# Create temporary download path
New-Item -Path $TemporaryDownloadPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create temporary download path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Test if the Custom Actions directory path exists
if (-not(Test-Path -Path $CustomActionScriptsDirectoryPath)) {
Write-LogEntry -Value "- Custom Actions directory path does not exist, creating: $($CustomActionScriptsDirectoryPath)" -Severity 1
try {
# Create Custom Actions directory path
New-Item -Path $CustomActionScriptsDirectoryPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create Custom Actions directory path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Test if Windows setup config file parent path exists
$WindowsSetupConfigPath = Split-Path -Path $WindowsSetupConfigFilePath -Parent
if (-not(Test-Path -Path $WindowsSetupConfigPath)) {
Write-LogEntry -Value "- Windows setup config file parent path does not exist, creating: $($WindowsSetupConfigPath)" -Severity 1
try {
# Create Windows setup config file parent path
New-Item -Path $WindowsSetupConfigPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create Windows setup config file parent path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Test if modules directory path exists
if (-not(Test-Path -Path $ModulesDirectoryPath)) {
Write-LogEntry -Value "- Modules directory path does not exist, creating: $($ModulesDirectoryPath)" -Severity 1
try {
# Create modules directory path
New-Item -Path $ModulesDirectoryPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create modules directory path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
#
# TODO: Add support for install drivers
#
# Check if prerequisites for script operation are met, if true continue script operation
if ($ScriptOperationPrerequisites -eq $true) {
try {
# Construct the storage account context
$StorageAccountContext = New-AzStorageContext -StorageAccountName $StorageAccountName -Anonymous -ErrorAction "Stop" -Verbose:$false
try {
# Download the latest version manifest file from storage account to the temporary download destination
$LatestVersionManifest = Get-AzStorageBlobContent -Container $StorageAccountContainer -Blob $ManifestFileName -Destination $TemporaryDownloadPath -Context $StorageAccountContext -ClientTimeoutPerRequest 30 -Force -ErrorAction "Stop" -Verbose:$false
# Test that the manifest file was downloaded successfully
$ManifestFilePath = Join-Path -Path $TemporaryDownloadPath -ChildPath $ManifestFileName
if (Test-Path -Path $ManifestFilePath) {
Write-LogEntry -Value "- Successfully downloaded the latest version manifest file" -Severity 1
try {
# Parse the manifest file to get the latest version details of each script modules
$ManifestContent = Get-Content -Path $ManifestFilePath -Raw -ErrorAction "Stop" | ConvertFrom-Json
Write-LogEntry -Value "- Successfully parsed the latest version manifest file" -Severity 1
# Handle output of UpdateNotifications prerequisites
Write-LogEntry -Value "[UpdateNotifications] - Initializing" -Severity 1
# Parse the manifest file to check if any UpdateNotifications instructions are present to be configure locally on the device
if ($ManifestContent.UpdateNotifications) {
Write-LogEntry -Value "- Update notification instructions are present in the manifest file" -Severity 1
foreach ($UpdateNotification in $ManifestContent.UpdateNotifications) {
Write-LogEntry -Value "- Processing current update notification configuration: $($UpdateNotification.Name)" -Severity 1
# Validate that current update notification contains required properties
if (-not($UpdateNotification.Name -and $UpdateNotification.KeyPath -and $UpdateNotification.DataValue -and $UpdateNotification.Type)) {
$ErrorMessage = "Update notification configuration is missing required properties. Required properties: Name, KeyPath, DataValue, Type"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
break
}
# Check if the update notification registry key exists
if (Test-Path -Path $UpdateNotification.KeyPath) {
Write-LogEntry -Value "- Update notification registry key already exists: $($UpdateNotification.KeyPath)" -Severity 1
}
else {
Write-LogEntry -Value "- Update notification registry key does not exist, creating: $($UpdateNotification.KeyPath)" -Severity 1
try {
# Create the update notification registry key
New-Item -Path $UpdateNotification.KeyPath -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created update notification registry key" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create update notification registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Check if the update notification registry key value exists
Write-LogEntry -Value "- Checking if update notification registry value '$($UpdateNotification.Name)' exists in: $($UpdateNotification.KeyPath)" -Severity 1
$UpdateNotificationKeyValuePresence = Get-ItemProperty -Path $UpdateNotification.KeyPath -Name $UpdateNotification.Name -ErrorAction "SilentlyContinue"
if ($UpdateNotificationKeyValuePresence -eq $null) {
Write-LogEntry -Value "- Update notification registry value does not exist, creating registry value '$($UpdateNotification.Name)' with data value '$($UpdateNotification.DataValue)' in: $($UpdateNotification.KeyPath)" -Severity 1
try {
# Create the update notification registry key
New-ItemProperty -Path $UpdateNotification.KeyPath -Name $UpdateNotification.Name -PropertyType $UpdateNotification.Type -Value $UpdateNotification.DataValue -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created update notification registry value" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create update notification registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Update notification registry value '$($UpdateNotification.Name)' exists in: $($UpdateNotification.KeyPath)" -Severity 1
# Check if registry data value conforms with the manifest file
Write-LogEntry -Value "- Checking if update notification registry value '$($UpdateNotification.Name)' data value matches value from manifest file: $($UpdateNotification.DataValue)" -Severity 1
$UpdateNotificationRegistryValueData = Get-ItemPropertyValue -Path $UpdateNotification.KeyPath -Name $UpdateNotification.Name
if ($UpdateNotificationRegistryValueData -ne $UpdateNotification.DataValue) {
Write-LogEntry -Value "- Current value of update notification '$($UpdateNotification.Name)' registry value: $($UpdateNotificationRegistryValueData)" -Severity 1
Write-LogEntry -Value "- Update notification registry value '$($UpdateNotification.Name)' data value does not match value from manifest file, updating to: $($UpdateNotification.DataValue)" -Severity 1
try {
# Update the update notification registry key value
Set-ItemProperty -Path $UpdateNotification.KeyPath -Name $UpdateNotification.Name -Value $UpdateNotification.DataValue -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully updated update notification registry value" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to update update notification registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Update notification registry value '$($UpdateNotification.Name)' data value matches value from manifest file: $($UpdateNotification.DataValue)" -Severity 1
}
}
}
}
else {
Write-LogEntry -Value "- No update notification instructions are present in the manifest file" -Severity 1
}
# Handle output of UpdateNotifications prerequisites
Write-LogEntry -Value "[UpdateNotifications] - Completed" -Severity 1
if ($ScriptOperationPrerequisites -eq $true) {
# Construct hashtable for SetupConfig parameters from manifest file
$WindowsSetupManifestParametersTable = @{}
foreach ($KeyValuePair in $ManifestContent.SetupConfig) {
$WindowsSetupManifestParametersTable.Add($KeyValuePair.Name, $KeyValuePair.Value)
}
# Handle output of Windows setup engine script prerequisites
Write-LogEntry -Value "[WindowsSetupEngine-Prerequisites] - Initializing" -Severity 1
# Detect if manifest file contains any paths to directories that must be created prior to script execution
$WindowsSetupParametersList = @("PostOOBE", "PostRollback", "CopyLogs")
foreach ($WindowsSetupParameter in $WindowsSetupParametersList) {
if ($WindowsSetupManifestParametersTable.ContainsKey($WindowsSetupParameter)) {
Write-LogEntry -Value "- Found Windows setup parameter '$($WindowsSetupParameter)' in manifest file, check if defined path exists" -Severity 1
$WindowsSetupParameterParentPath = Split-Path -Path $WindowsSetupManifestParametersTable[$WindowsSetupParameter] -Parent
Write-LogEntry -Value "- Checking if parent path '$($WindowsSetupParameterParentPath)' for '$($WindowsSetupParameter)' exists" -Severity 1
if (-not(Test-Path -Path $WindowsSetupParameterParentPath)) {
Write-LogEntry -Value "- Path '$($WindowsSetupParameterParentPath)' does not exist, creating: $($WindowsSetupParameterParentPath)" -Severity 1
try {
New-Item -Path $WindowsSetupParameterParentPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create path '$($WindowsSetupParameterParentPath)'. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
}
}
if ($ScriptOperationPrerequisites -eq $true) {
try {
# Generate SetupConfig.ini file with data from the manifest file
Write-LogEntry -Value "- Generating the SetupConfig.ini file" -Severity 1
$SetupConfigData = [System.Collections.Specialized.OrderedDictionary]::new()
foreach ($KeyValuePair in $ManifestContent.SetupConfig) {
Write-LogEntry -Value "- Adding key '$($KeyValuePair.Name)' with value: $($KeyValuePair.Value)" -Severity 1
$SetupConfigData.Add($KeyValuePair.Name, $KeyValuePair.Value)
}
Write-LogEntry -Value "- Successfully added all keys and values to the SetupConfig.ini data construct" -Severity 1
Out-SetupConfigIniFile -Path $WindowsSetupConfigFilePath -Value $SetupConfigData -ErrorAction "Stop"
Write-LogEntry -Value "- Successfully generated the SetupConfig.ini file" -Severity 1
# Determine if manifest file contains specifications to for the SetupComplete script files to be created, where POSTOOBE as setup parameter is defined in the SetupConfig data
if ($SetupConfigData["POSTOOBE"]) {
Write-LogEntry -Value "- POSTOOBE setup parameter is defined in the manifest file, check if SetupComplete script have been defined" -Severity 1
foreach ($SetupConfigScriptFile in $ManifestContent.SetupConfigScriptFiles) {
if ($SetupConfigScriptFile.Type -eq "POSTOOBE") {
$SetupCompleteCmdFilePath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath "SetupComplete.cmd"
Write-LogEntry -Value "- Found POSTOOBE script file in manifest file, check if SetupComplete script file exists in: $($ProgramDataFeatureUpdateControllerRootPath)" -Severity 1
# Determine the SetupComplete script file name
$SetupConfigScriptFileName = $ManifestContent.SetupConfigScriptFiles | Where-Object { $PSItem.Type -eq "POSTOOBE" } | Select-Object -ExpandProperty "ScriptFile"
if ($SetupConfigScriptFileName -ne $null) {
# Construct the SetupComplete.cmd file in the ProgramData directory
if (-not(Test-Path -Path $SetupCompleteCmdFilePath)) {
Write-LogEntry -Value "- SetupComplete.cmd file does not exist, creating: $($SetupCompleteCmdFilePath)" -Severity 1
try {
# Create the SetupComplete.cmd file
New-Item -Path $SetupCompleteCmdFilePath -ItemType File -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created SetupComplete.cmd file" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create SetupComplete.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
try {
# Add the command to be executed to the SetupComplete.cmd file
$SetupCompleteCmdFileContent = "powershell.exe -ExecutionPolicy Bypass -NoProfile -File ""$($ProgramDataFeatureUpdateControllerRootPath)\$($SetupConfigScriptFileName)"" -WindowStyle Hidden"
Write-LogEntry -Value "- Adding command to SetupComplete.cmd file: $($SetupCompleteCmdFileContent)" -Severity 1
Add-Content -Path $SetupCompleteCmdFilePath -Value $SetupCompleteCmdFileContent -ErrorAction "Stop"
Write-LogEntry -Value "- Successfully added command to SetupComplete.cmd file" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to add powershell.exe command to SetupComplete.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- SetupComplete.cmd file already exists, but command line update may be required" -Severity 1
# Check if the script file name in the SetupComplete.cmd file is the same as the one in the manifest file
$SetupCompleteCmdFileContent = Get-Content -Path $SetupCompleteCmdFilePath -ErrorAction "Stop"
$SetupCompleteCmdFileContent = $SetupCompleteCmdFileContent | Where-Object { $PSItem -match "powershell.exe" }
$SetupCompleteCmdFileContent = $SetupCompleteCmdFileContent -replace "[^\\]+(?=\.ps1$)", [System.IO.Path]::GetFileNameWithoutExtension($SetupConfigScriptFileName)
try {
# Update the command to be executed in the SetupComplete.cmd file
Write-LogEntry -Value "- Updating SetupComplete.cmd command line to: $($SetupCompleteCmdFileContent)" -Severity 1
Set-Content -Path $SetupCompleteCmdFilePath -Value $SetupCompleteCmdFileContent -ErrorAction "Stop"
}
catch [System.Exception] {
$ErrorMessage = "Failed to update SetupComplete.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
try {
# Download SetupComplete script file from storage account to the feature update controller root directory
$SetupCompletePs1FilePath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath $SetupConfigScriptFileName
Write-LogEntry -Value "- Downloading '$($SetupConfigScriptFileName)' file from storage account to: $($SetupCompletePs1FilePath)" -Severity 1
$SetupCompletePs1File = Get-AzStorageBlobContent -Container $StorageAccountContainer -Blob $SetupConfigScriptFileName -Destination $ProgramDataFeatureUpdateControllerRootPath -Context $StorageAccountContext -ClientTimeoutPerRequest 30 -Force -ErrorAction "Stop" -Verbose:$false
}
catch [System.Exception] {
$ErrorMessage = "Failed to download $($SetupConfigScriptFileName) file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Script file name for type POSTOOBE is not defined in the manifest file, skipping file creation and download process" -Severity 1
}
}
else {
Write-LogEntry -Value "- POSTOOBE type was not defined in SetupConfig parameters in manifest file, skipping" -Severity 1
}
}
}
# Determine if manifest file contains specifications to for the SetupComplete script files to be created, where PostRollback as setup parameter is defined in the SetupConfig data
if ($SetupConfigData["PostRollback"]) {
Write-LogEntry -Value "- PostRollback setup parameter is defined in the manifest file, check if SetupRollback script have been defined" -Severity 1
foreach ($SetupConfigScriptFile in $ManifestContent.SetupConfigScriptFiles) {
if ($SetupConfigScriptFile.Type -eq "PostRollback") {
$SetupRollbackCmdFilePath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath "SetupRollback.cmd"
Write-LogEntry -Value "- Found PostRollback script file in manifest file, check if SetupRollback script file exists in: $($ProgramDataFeatureUpdateControllerRootPath)" -Severity 1
# Determine the PostRollback script file name
$SetupConfigScriptFileName = $ManifestContent.SetupConfigScriptFiles | Where-Object { $PSItem.Type -eq "PostRollback" } | Select-Object -ExpandProperty "ScriptFile"
if ($SetupConfigScriptFileName -ne $null) {
# Construct the PostRollback associated command file in the ProgramData directory
if (-not(Test-Path -Path $SetupRollbackCmdFilePath)) {
Write-LogEntry -Value "- SetupRollback.cmd file does not exist, creating: $($SetupRollbackCmdFilePath)" -Severity 1
try {
# Create the PostRollback associated command file
New-Item -Path $SetupRollbackCmdFilePath -ItemType File -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created SetupRollback.cmd file" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create SetupRollback.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
try {
# Add the command to be executed to the SetupRollback.cmd file
$SetupRollbackCmdFileContent = "powershell.exe -ExecutionPolicy Bypass -NoProfile -File ""$($ProgramDataFeatureUpdateControllerRootPath)\$($SetupConfigScriptFileName)"" -WindowStyle Hidden"
Write-LogEntry -Value "- Adding command to SetupRollback.cmd file: $($SetupRollbackCmdFileContent)" -Severity 1
Add-Content -Path $SetupRollbackCmdFilePath -Value $SetupRollbackCmdFileContent -ErrorAction "Stop"
Write-LogEntry -Value "- Successfully added command to SetupRollback.cmd file" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to add powershell.exe command to SetupRollback.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- SetupRollback.cmd file already exists, but command line update may be required" -Severity 1
# Check if the script file name in the SetupRollback.cmd file is the same as the one in the manifest file
$SetupRollbackCmdFileContent = Get-Content -Path $SetupRollbackCmdFilePath -ErrorAction "Stop"
$SetupRollbackCmdFileContent = $SetupRollbackCmdFileContent | Where-Object { $PSItem -match "powershell.exe" }
$SetupRollbackCmdFileContent = $SetupRollbackCmdFileContent -replace "[^\\]+(?=\.ps1$)", [System.IO.Path]::GetFileNameWithoutExtension($SetupConfigScriptFileName)
try {
# Update the command to be executed in the SetupRollback.cmd file
Write-LogEntry -Value "- Updating SetupRollback.cmd command line to: $($SetupRollbackCmdFileContent)" -Severity 1
Set-Content -Path $SetupRollbackCmdFilePath -Value $SetupRollbackCmdFileContent -ErrorAction "Stop"
}
catch [System.Exception] {
$ErrorMessage = "Failed to update SetupRollback.cmd file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
try {
# Download SetupRollback script file from storage account to the feature update controller root directory
$SetupConfigScriptFileName = $ManifestContent.SetupConfigScriptFiles | Where-Object { $PSItem.Type -eq "PostRollback" } | Select-Object -ExpandProperty "ScriptFile"
$SetupRollbackPs1FilePath = Join-Path -Path $ProgramDataFeatureUpdateControllerRootPath -ChildPath $SetupConfigScriptFileName
Write-LogEntry -Value "- Downloading '$($SetupConfigScriptFileName)' file from storage account to: $($SetupRollbackPs1FilePath)" -Severity 1
$SetupRollbackPs1File = Get-AzStorageBlobContent -Container $StorageAccountContainer -Blob $SetupConfigScriptFileName -Destination $ProgramDataFeatureUpdateControllerRootPath -Context $StorageAccountContext -ClientTimeoutPerRequest 30 -Force -ErrorAction "Stop" -Verbose:$false
}
catch [System.Exception] {
$ErrorMessage = "Failed to download $($SetupConfigScriptFileName) file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Script file name for type PostRollback is not defined in the manifest file, skipping file creation and download process" -Severity 1
}
}
else {
Write-LogEntry -Value "- PostRollback type was not defined in SetupConfig parameters in manifest file, skipping" -Severity 1
}
}
}
# Handle output of Windows setup engine script prerequisites
Write-LogEntry -Value "[WindowsSetupEngine-Prerequisites] - Completed" -Severity 1
# Handle output of custom actions prerequisites
Write-LogEntry -Value "[CustomActions-Install] - Initializing" -Severity 1
# Create and download the required custom actions in the manifest file section named CustomActions
if ($ManifestContent.CustomActions.Count -ge 1) {
Write-LogEntry -Value "- Found $($ManifestContent.CustomActions.Count) custom actions in the manifest file" -Severity 1
# Declare variable path for custom actions directory
$CustomActionsRootPath = Join-Path -Path $env:Windir -ChildPath "System32\update"
# Check if the custom actions are set to force update, if true delete existing custom actions
if ($ManifestContent.CustomActionsConfig.ForceUpdate -eq $true) {
Write-LogEntry -Value "- Custom actions are set to force update, deleting existing custom actions" -Severity 1
try {
# Remove custom actions unique folder registry key
$CustomActionRootRegistryKey = Join-Path -Path $RegistryRootKey -ChildPath "CustomActions"
if (Test-Path -Path $CustomActionRootRegistryKey) {
Write-LogEntry -Value "- Removing custom action registry root key: $($CustomActionRootRegistryKey)" -Severity 1
Remove-Item -Path $CustomActionRootRegistryKey -Recurse -Force -ErrorAction "Stop" | Out-Null
}
else {
Write-LogEntry -Value "- Custom action registry root key does not exist, skipping removal" -Severity 1
}
}
catch [System.Exception] {
$ErrorMessage = "Failed to remove custom action registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
try {
# Cleanup the System32\update directory
Write-LogEntry -Value "- Cleaning up the custom actions directory: $($CustomActionsRootPath)" -Severity 1
if (Test-Path -Path $CustomActionsRootPath) {
Remove-Item -Path "$($CustomActionsRootPath)\*" -Recurse -Force -ErrorAction "Stop" | Out-Null
}
else {
Write-LogEntry -Value "- Custom actions directory does not exist, skipping cleanup" -Severity 1
}
}
catch [System.Exception] {
$ErrorMessage = "Failed to cleanup the custom actions directory. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
try {
# Cleanup the feature update controller custom actions directory
Write-LogEntry -Value "- Cleaning up the custom actions directory: $($CustomActionScriptsDirectoryPath)" -Severity 1
if (Test-Path -Path $CustomActionScriptsDirectoryPath) {
Remove-Item -Path "$($CustomActionScriptsDirectoryPath)\*" -Recurse -Force -ErrorAction "Stop" | Out-Null
}
else {
Write-LogEntry -Value "- Custom actions directory does not exist, skipping cleanup" -Severity 1
}
}
catch [System.Exception] {
$ErrorMessage = "Failed to cleanup the custom actions directory. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Register the unique folder name in the registry
$CustomActionRootRegistryKey = Join-Path -Path $RegistryRootKey -ChildPath "CustomActions"
if (-not(Test-Path -Path $CustomActionRootRegistryKey)) {
Write-LogEntry -Value "- Custom action unique folder registry key does not exist, creating: $($CustomActionRootRegistryKey)" -Severity 1
try {
# Create custom action unique folder registry key
Write-LogEntry -Value "- Creating custom action root registry key: $($CustomActionRootRegistryKey)" -Severity 1
New-Item -Path $CustomActionRootRegistryKey -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action root registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action registry key already exists: $($CustomActionRootRegistryKey)" -Severity 1
}
# Loop through each custom action in the manifest file
foreach ($CustomAction in $ManifestContent.CustomActions) {
Write-LogEntry -Value "- Processing custom action: $($CustomAction.Name)" -Severity 1
# Declare variable for custom action type
$CustomActionType = $CustomAction.Type.ToLower()
# Declare variable for custom action name
$CustomActionName = $CustomAction.Name
# Check if the custom action registry root key contains a a sub-key with a known unique folder name in GUID format
Write-LogEntry -Value "- Checking if custom action registry root key contains a sub-key with a known unique folder name in GUID format" -Severity 1
$UniqueFolderName = Get-ChildItem -Path $CustomActionRootRegistryKey -ErrorAction "SilentlyContinue" | Where-Object { $PSItem.PSChildName -match "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$" }
if ($UniqueFolderName -ne $null) {
$UniqueFolderName = $UniqueFolderName.PSChildName
Write-LogEntry -Value "- Found unique folder name in custom action registry root key: $($UniqueFolderName)" -Severity 1
}
else {
Write-LogEntry -Value "- No unique folder name found in custom action registry root key" -Severity 1
$UniqueFolderName = (New-Guid).Guid
Write-LogEntry -Value "- Generated unique folder name for custom action type of : $($UniqueFolderName)" -Severity 1
}
# Check if the custom action unique folder registry key exists for current custom action
$CustomActionUniqueFolderRegistryKey = Join-Path -Path $CustomActionRootRegistryKey -ChildPath $UniqueFolderName
if (-not(Test-Path -Path $CustomActionUniqueFolderRegistryKey)) {
Write-LogEntry -Value "- Custom action unique folder registry key does not exist, creating: $($CustomActionUniqueFolderRegistryKey)" -Severity 1
try {
# Create custom action unique folder registry key
Write-LogEntry -Value "- Creating custom action unique folder registry key: $($CustomActionUniqueFolderRegistryKey)" -Severity 1
New-Item -Path $CustomActionUniqueFolderRegistryKey -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created custom action unique folder registry key" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action unique folder registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action unique folder registry key already exists: $($CustomActionUniqueFolderRegistryKey)" -Severity 1
}
# Check if the custom action type registry key exists for current custom action
$CustomActionTypeRegistryKey = Join-Path -Path $CustomActionUniqueFolderRegistryKey -ChildPath $CustomActionType
if (-not(Test-Path -Path $CustomActionTypeRegistryKey)) {
Write-LogEntry -Value "- Custom action type registry key does not exist, creating: $($CustomActionTypeRegistryKey)" -Severity 1
try {
# Create custom action type registry key
Write-LogEntry -Value "- Creating custom action type registry key: $($CustomActionTypeRegistryKey)" -Severity 1
New-Item -Path $CustomActionTypeRegistryKey -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created custom action type registry key" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action type registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action type registry key already exists: $($CustomActionTypeRegistryKey)" -Severity 1
}
# Check if the custom action type registry key contains the required registry key for the custom action name
$CustomActionNameRegistryKey = Join-Path -Path $CustomActionTypeRegistryKey -ChildPath $CustomActionName.ToLower()
if (-not(Test-Path -Path $CustomActionNameRegistryKey)) {
Write-LogEntry -Value "- Custom action name registry key does not exist, creating: $($CustomActionNameRegistryKey)" -Severity 1
try {
# Create custom action name registry key
New-Item -Path $CustomActionNameRegistryKey -Force -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully created custom action name registry key" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action name registry key. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action name registry key already exists: $($CustomActionNameRegistryKey)" -Severity 1
}
# Check if the custom action registry key contains the required registry ScriptFile value
Write-LogEntry -Value "- Checking if custom action registry key contains the required registry ScriptFile value" -Severity 1
$CustomActionScriptFileValue = Get-ItemProperty -Path $CustomActionNameRegistryKey -Name "ScriptFile" -ErrorAction "SilentlyContinue"
if ($CustomActionScriptFileValue -eq $null) {
Write-LogEntry -Value "- No custom action registry value was found with name: ScriptFile" -Severity 1
try {
# Set the custom action name registry value
$CustomActionScriptFilePath = Join-Path -Path $CustomActionScriptsDirectoryPath -ChildPath $CustomAction.ScriptFile
Write-LogEntry -Value "- Setting custom action registry value 'ScriptFile' with value: $($CustomActionScriptFilePath)" -Severity 1
New-ItemProperty -Path $CustomActionNameRegistryKey -Name "ScriptFile" -Value $CustomActionScriptFilePath -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully added custom action registry value" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to add custom action registry value. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action registry value already exists with value: $($CustomActionScriptFilePath)" -Severity 1
}
# Declare variable for custom action directory path based on type
$CustomActionFolderPath = Join-Path -Path (Join-Path -Path $CustomActionsRootPath -ChildPath $CustomActionType) -ChildPath $UniqueFolderName
# Declare variables for ScriptWrapperFile and path
$CustomActionScriptWrapperFileName = ("$($CustomActionName).cmd").ToLower()
$CustomActionScriptWrapperFilePath = Join-Path -Path $CustomActionFolderPath -ChildPath $CustomActionScriptWrapperFileName
# Check if the custom action registry key contains the required registry ScriptWrapperFile value
Write-LogEntry -Value "- Checking if custom action registry key contains the required registry ScriptWrapperFile value" -Severity 1
$CustomActionScriptWrapperFileValue = Get-ItemProperty -Path $CustomActionNameRegistryKey -Name "ScriptWrapperFile" -ErrorAction "SilentlyContinue"
if ($CustomActionScriptWrapperFileValue -eq $null) {
Write-LogEntry -Value "- No custom action registry value was found with name: ScriptWrapperFile" -Severity 1
try {
# Set the custom action name registry value
Write-LogEntry -Value "- Setting custom action registry value 'ScriptWrapperFile' with value: $($CustomActionScriptWrapperFilePath)" -Severity 1
New-ItemProperty -Path $CustomActionNameRegistryKey -Name "ScriptWrapperFile" -Value $CustomActionScriptWrapperFilePath -ErrorAction "Stop" | Out-Null
Write-LogEntry -Value "- Successfully added custom action registry value" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to add custom action registry value. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action registry value already exists with value: $($CustomActionScriptWrapperFilePath)" -Severity 1
}
# Check if the custom action directory path exists
if (-not(Test-Path -Path $CustomActionFolderPath)) {
Write-LogEntry -Value "- Custom action directory path does not exist, creating: $($CustomActionFolderPath)" -Severity 1
try {
# Create custom action directory path
New-Item -Path $CustomActionFolderPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action directory path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
# Continue if the custom action directory path exists
if (Test-Path -Path $CustomActionFolderPath) {
Write-LogEntry -Value "- Custom action directory path exists: $($CustomActionFolderPath)" -Severity 1
# Construct the custom action script wrapper command file
$CustomActionScriptWrapperFileName = ("$($CustomActionName).cmd").ToLower()
$CustomActionScriptWrapperFilePath = Join-Path -Path $CustomActionFolderPath -ChildPath $CustomActionScriptWrapperFileName
# Check if the custom action script wrapper command file exists
if (-not(Test-Path -Path $CustomActionScriptWrapperFilePath)) {
Write-LogEntry -Value "- Custom action script wrapper command file '$($CustomActionScriptWrapperFileName)' does not exist, creating: $($CustomActionScriptWrapperFilePath)" -Severity 1
try {
# Create custom action script wrapper command file
New-Item -Path $CustomActionScriptWrapperFilePath -ItemType "File" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create custom action script wrapper command file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
try {
# Add the custom action script wrapper command to the custom action script wrapper command file
$CustomActionScriptWrapperCommand = "powershell.exe -ExecutionPolicy Bypass -NoProfile -File ""$($CustomActionScriptsDirectoryPath)\$($CustomAction.ScriptFile)"" -WindowStyle Hidden"
Write-LogEntry -Value "- Adding custom action script wrapper command to: $($CustomActionScriptWrapperCommand)" -Severity 1
Add-Content -Path $CustomActionScriptWrapperFilePath -Value $CustomActionScriptWrapperCommand -ErrorAction "Stop"
Write-LogEntry -Value "- Successfully added custom action script wrapper command to: $($CustomActionScriptWrapperFilePath)" -Severity 1
}
catch [System.Exception] {
$ErrorMessage = "Failed to add custom action script wrapper command to custom action script wrapper command file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action script wrapper command file '$($CustomActionScriptWrapperFileName)' already exists, but command line update may be required" -Severity 1
# Check if the script file name in the Custom Action command file is the same as the one in the manifest file
$CustomActionScriptWrapperFileContent = Get-Content -Path $CustomActionScriptWrapperFilePath -ErrorAction "Stop"
$CustomActionScriptWrapperFileContent = $CustomActionScriptWrapperFileContent | Where-Object { $PSItem -match "powershell.exe" }
$CustomActionScriptWrapperFileContent = $CustomActionScriptWrapperFileContent -replace "[^\\]+(?=\.ps1$)", [System.IO.Path]::GetFileNameWithoutExtension($CustomAction.ScriptFile)
try {
# Update the command to be executed in the custom action script wrapper command file
Write-LogEntry -Value "- Updating custom action script wrapper command line to: $($CustomActionScriptWrapperFileContent)" -Severity 1
Set-Content -Path $CustomActionScriptWrapperFilePath -Value $CustomActionScriptWrapperFileContent -ErrorAction "Stop"
}
catch [System.Exception] {
$ErrorMessage = "Failed to update custom action script wrapper command file. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
try {
# Download the custom action script file from the storage account to the custom action directory
Write-LogEntry -Value "- Downloading custom action script '$($CustomAction.ScriptFile)' from storage account to: $($CustomActionScriptsDirectoryPath)" -Severity 1
$CustomActionScriptFile = Get-AzStorageBlobContent -Container $StorageAccountContainer -Blob $CustomAction.ScriptFile -Destination $CustomActionScriptsDirectoryPath -Context $StorageAccountContext -ClientTimeoutPerRequest 30 -Force -ErrorAction "Stop" -Verbose:$false
}
catch [System.Exception] {
$ErrorMessage = "Failed to download custom action script '$($CustomAction.ScriptFile)'. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
$ScriptOperationPrerequisites = $false
}
}
else {
Write-LogEntry -Value "- Custom action directory path does not exist, skipping custom action script wrapper command file creation" -Severity 1
$ScriptOperationPrerequisites = $false
}
}
}
else {
# No custom actions defined in the manifest file
Write-LogEntry -Value "- No custom actions defined in the manifest file" -Severity 1
}
# Handle output of custom actions prerequisites
Write-LogEntry -Value "[CustomActions-Install] - Completed" -Severity 1
if ($ScriptOperationPrerequisites -eq $true) {
Write-LogEntry -Value "[ScriptModule-Install] - Initializing" -Severity 1
if ($ManifestContent.Modules.Count -ge 1) {
Write-LogEntry -Value "- Found '$($ManifestContent.Modules.Count)' script modules in the manifest file" -Severity 1
try {
# Process each script module from the manifest file
foreach ($ScriptModule in $ManifestContent.Modules) {
# Construct the current module directory path
$ModuleDirectoryPath = Join-Path -Path $ModulesDirectoryPath -ChildPath $ScriptModule.Name
# Construct the current module registry key
$ModuleRegistryKey = Join-Path -Path $RegistryRootKey -ChildPath (Join-Path -Path "Modules" -ChildPath $ScriptModule.Name)
# Check if the current script module is already installed, by checking for the presence of the version registry value
Write-LogEntry -Value "- Checking if script module '$($ScriptModule.Name)' is installed" -Severity 1
if (-not(Test-Path -Path $ModuleRegistryKey)) {
Write-LogEntry -Value "- Script module '$($ScriptModule.Name)' is not installed, installing module" -Severity 1
try {
# Check if module specific directory exists in the modules directory path
if (-not(Test-Path -Path $ModuleDirectoryPath)) {
Write-LogEntry -Value "- Module directory path does not exist, creating: $($ModuleDirectoryPath)" -Severity 1
try {
# Create module directory path
New-Item -Path $ModuleDirectoryPath -ItemType "Directory" -Force -ErrorAction "Stop" | Out-Null
}
catch [System.Exception] {
$ErrorMessage = "Failed to create module directory path. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
}
}
try {
# Download the script module from the storage account to the module specific directory
Write-LogEntry -Value "- Downloading script module '$($ScriptModule.Name)' from storage account to: $($ModuleDirectoryPath)" -Severity 1
$ModuleScriptFile = Get-AzStorageBlobContent -Container $StorageAccountContainer -Blob $ScriptModule.ScriptFile -Destination $ModuleDirectoryPath -Context $StorageAccountContext -ClientTimeoutPerRequest 30 -Force -ErrorAction "Stop" -Verbose:$false
}
catch [System.Exception] {
$ErrorMessage = "Failed to download script module '$($ScriptModule.Name)'. Error message: $($_.Exception.Message)"
Write-LogEntry -Value "- $($ErrorMessage)" -Severity 3
}
try {
# Download the support file from the storage account to the module specific directory, if defined
if (($ScriptModule.SupportFile) -and (-not([string]::IsNullOrEmpty($ScriptModule.SupportFile)))) {