forked from MSEndpointMgr/ModernDriverManagement
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Invoke-CMApplyDriverPackage.ps1
2293 lines (1964 loc) · 120 KB
/
Invoke-CMApplyDriverPackage.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
Download driver package (regular package) matching computer model, manufacturer and operating system.
.DESCRIPTION
This script will determine the model of the computer, manufacturer and operating system being deployed and then query
the specified AdminService endpoint for a list of Packages. It then sets the OSDDownloadDownloadPackages variable
to include the PackageID property of a package matching the computer model. If multiple packages are detect, it will select
most current one by the creation date of the packages.
.PARAMETER BareMetal
Set the script to operate in 'BareMetal' deployment type mode.
.PARAMETER DriverUpdate
Set the script to operate in 'DriverUpdate' deployment type mode.
.PARAMETER OSUpgrade
Set the script to operate in 'OSUpgrade' deployment type mode.
.PARAMETER PreCache
Set the script to operate in 'PreCache' deployment type mode.
.PARAMETER XMLPackage
Set the script to operate in 'XMLPackage' deployment type mode.
.PARAMETER DebugMode
Set the script to operate in 'DebugMode' deployment type mode.
.PARAMETER Endpoint
Specify the internal fully qualified domain name of the server hosting the AdminService, e.g. CM01.domain.local.
.PARAMETER XMLDeploymentType
Specify the deployment type mode for XML based driver package deployments, e.g. 'BareMetal', 'OSUpdate', 'DriverUpdate', 'PreCache'.
.PARAMETER UserName
Specify the service account user name used for authenticating against the AdminService endpoint.
.PARAMETER Password
Specify the service account password used for authenticating against the AdminService endpoint.
.PARAMETER Filter
Define a filter used when calling ConfigMgr WebService to only return objects matching the filter.
.PARAMETER TargetOSName
Define the value that will be used as the target operating system name e.g. 'Windows 10'.
.PARAMETER TargetOSVersion
Define the value that will be used as the target operating system version e.g. '2004'.
.PARAMETER TargetOSArchitecture
Define the value that will be used as the target operating system architecture e.g. 'x64'.
.PARAMETER OperationalMode
Define the operational mode, either Production or Pilot, for when calling ConfigMgr WebService to only return objects matching the selected operational mode.
.PARAMETER UseDriverFallback
Specify if the script is to be used with a driver fallback package when a driver package for SystemSKU or computer model could not be detected.
.PARAMETER DriverInstallMode
Specify whether to install drivers using DISM.exe with recurse option or spawn a new process for each driver.
.PARAMETER PreCachePath
Specify a custom path for the PreCache directory, overriding the default CCMCache directory.
.PARAMETER Manufacturer
Override the automatically detected computer manufacturer when running in debug mode.
.PARAMETER ComputerModel
Override the automatically detected computer model when running in debug mode.
.PARAMETER SystemSKU
Override the automatically detected SystemSKU when running in debug mode.
.PARAMETER OSVersionFallback
Use this switch to check for drivers packages that matches earlier versions of Windows than what's specified as input for TargetOSVersion.
.EXAMPLE
# Detect, download and apply drivers during OS deployment with ConfigMgr:
.\Invoke-CMApplyDriverPackage.ps1 -BareMetal -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion '1909'
# Detect, download and apply drivers during OS deployment with ConfigMgr and use a driver fallback package if no matching driver package can be found:
.\Invoke-CMApplyDriverPackage.ps1 -BareMetal -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion '1909' -UseDriverFallback
# Detect, download and apply drivers during OS deployment with ConfigMgr and check for driver packages that matches an earlier version than what's specified for TargetOSVersion:
.\Invoke-CMApplyDriverPackage.ps1 -BareMetal -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion 1909 -OSVersionFallback
# Detect and download drivers during OS upgrade with ConfigMgr:
.\Invoke-CMApplyDriverPackage.ps1 -OSUpgrade -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion '1909'
# Detect, download and update a device with latest drivers for an running operating system using ConfigMgr:
.\Invoke-CMApplyDriverPackage.ps1 -DriverUpdate -Endpoint 'CM01.domain.com'
# Detect and download (pre-caching content) during OS upgrade with ConfigMgr:
.\Invoke-CMApplyDriverPackage.ps1 -PreCache -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion '1909'
# Detect and download (pre-caching content) to a custom path during OS upgrade with ConfigMgr:
.\Invoke-CMApplyDriverPackage.ps1 -PreCache -Endpoint 'CM01.domain.com' -TargetOSName 'Windows 10' -TargetOSVersion '1909' -PreCachePath 'C:\Windows\Temp\DriverPackage'
# Run in a debug mode for testing purposes (to be used locally on the computer model):
.\Invoke-CMApplyDriverPackage.ps1 -DebugMode -Endpoint 'CM01.domain.com' -UserName '[email protected]' -Password 'svc-password' -TargetOSName 'Windows 10' -TargetOSVersion '1909'
# Run in a debug mode for testing purposes and overriding the automatically detected computer details (could be executed basically anywhere):
.\Invoke-CMApplyDriverPackage.ps1 -DebugMode -Endpoint 'CM01.domain.com' -UserName '[email protected]' -Password 'svc-password' -TargetOSName 'Windows 10' -TargetOSVersion '1909' -Manufacturer 'Dell' -ComputerModel 'Precision 5520' -SystemSKU '07BF'
# Detect, download and apply drivers during OS deployment with ConfigMgr and use an XML package as the source of driver package details instead of the AdminService:
.\Invoke-CMApplyDriverPackage.ps1 -XMLPackage -XMLDeploymentType BareMetal -TargetOSName 'Windows 10' -TargetOSVersion '1909' -TargetOSArchitecture 'x64'
.NOTES
FileName: Invoke-CMApplyDriverPackage.ps1
Author: Nickolaj Andersen / Maurice Daly
Contact: @NickolajA / @MoDaly_IT
Created: 2017-03-27
Updated: 2022-03-05
Contributors: @CodyMathis123, @JamesMcwatty
Version history:
1.0.0 - (2017-03-27) - Script created
1.0.1 - (2017-04-18) - Updated script with better support for multiple vendor entries
1.0.2 - (2017-04-22) - Updated script with support for multiple operating systems driver packages, e.g. Windows 8.1 and Windows 10
1.0.3 - (2017-05-03) - Updated script with support for manufacturer specific Windows 10 versions for HP and Microsoft
1.0.4 - (2017-05-04) - Updated script to trim any white spaces trailing the computer model detection from WMI
1.0.5 - (2017-05-05) - Updated script to pull the model for Lenovo systems from the correct WMI class
1.0.6 - (2017-05-22) - Updated script to detect the proper package based upon OS Image version referenced in task sequence when multiple packages are detected
1.0.7 - (2017-05-26) - Updated script to filter OS when multiple model matches are found for different OS platforms
1.0.8 - (2017-06-26) - Updated script with improved computer name matching when filtering out packages returned from the web service
1.0.9 - (2017-08-25) - Updated script to read package description for Microsoft models in order to match the WMI value contained within
1.1.0 - (2017-08-29) - Updated script to only check for the OS build version instead of major, minor, build and revision for HP systems. $OSImageVersion will now only contain the most recent version if multiple OS images is referenced in the Task Sequence
1.1.1 - (2017-09-12) - Updated script to match the system SKU for Dell, Lenovo and HP models. Added architecture check for matching packages
1.1.2 - (2017-09-15) - Replaced computer model matching with SystemSKU. Added script with support for different exit codes
1.1.3 - (2017-09-18) - Added support for downloading package content instead of setting OSDDownloadDownloadPackages variable
1.1.4 - (2017-09-19) - Added support for installing driver package directly from this script instead of running a seperate DISM command line step
1.1.5 - (2017-10-12) - Added support for in full OS driver maintenance updates
1.1.6 - (2017-10-29) - Fixed an issue when detecting Microsoft manufacturer information
1.1.7 - (2017-10-29) - Changed the OSMaintenance parameter from a string to a switch object, make sure that your implementation of this is amended in any task sequence steps
1.1.8 - (2017-11-07) - Added support for driver fallback packages when the UseDriverFallback param is used
1.1.9 - (2017-12-12) - Added additional output for failure to detect system SKU value from WMI
1.2.0 - (2017-12-14) - Fixed an issue where the HP packages would not properly be matched against the OS image version returned by the web service
1.2.1 - (2018-01-03) - IMPORTANT - OSMaintenance switch has been replaced by the DeploymentType parameter. In order to support the default behavior (BareMetal), OSUpgrade and DriverUpdate operational
modes for the script, this change was required. Update your task sequence configuration before you use this update.
2.0.0 - (2018-01-10) - Updates include support for machines with blank system SKU values and the ability to run BIOS & driver updates in the FULL OS
2.0.1 - (2018-01-18) - Fixed a regex issue when attempting to fallback to computer model instead of SystemSKU
2.0.2 - (2018-01-24) - Re-constructed the logic for matching driver package to begin with computer model or SystemSKU (SystemSKU takes precedence before computer model) and improved the logging when matching for driver packages
2.0.3 - (2018-01-25) - Added a fix for multiple manufacturer package matches not working for Windows 7. Fixed an issue where SystemSKU was used and multiple driver packages matched. Added script line logging when the script cought an exception.
2.0.4 - (2018-01-26) - Changed from using a foreach loop to a for loop in reverse to remove driver packages that was matched by SystemSKU but does not match the computer model
2.0.5 - (2018-01-29) - Replaced Add-Content with Out-File for issue with file lock causing not all log entries to be written to the ApplyDriverPackage.log file
2.0.6 - (2018-02-21) - Updated to cater for the presence of underscores in Microsoft Surface models
2.0.7 - (2018-02-25) - Added support for a DebugMode switch for running script outside of a task sequence for driver package detection
2.0.8 - (2018-02-25) - Added a check to bail out the script if computer model and SystemSKU are null or an empty string
2.0.9 - (2018-05-07) - Removed exit code 34 event. DISM will now continue to process drivers if a single or multiple failures occur in order to proceed with the task sequence
2.1.0 - (2018-06-01) - IMPORTANT: From this version, ConfigMgr WebService 1.6 is required. Added a new parameter named OSImageTSVariableName that accepts input of a task sequence variable. This task sequence variable should contain the OS Image package ID of
the desired Operating System Image selected in an Apply Operating System step. This new functionality allows for using multiple Apply Operating System steps in a single task sequence. Added Panasonic for manufacturer detection.
Improved logic with fallback from SystemSKU to computer model. Script will now fall back to computer model if there was no match to the SystemSKU. This still requires that the SystemSKU contains a value and is not null or empty, otherwise
the logic will directly fall back to computer model. A new parameter named DriverInstallMode has been added to control how drivers are installed for BareMetal deployment. Valid inputs are Single or Recurse.
2.1.1 - (2018-08-28) - Code tweaks and changes for Windows build to version switch in the Driver Automation Tool. Improvements to the SystemSKU reverse section for HP models and multiple SystemSKU values from WMI
2.1.2 - (2018-08-29) - Added code to handle Windows 10 version specific matching and also support matching for the name only
2.1.3 - (2018-09-03) - Code tweak to Windows 10 version matching process
2.1.4 - (2018-09-18) - Added support to override the task sequence package ID retrieved from _SMSTSPackageID when the Apply Operating System step is in a child task sequence
2.1.5 - (2018-09-18) - Updated the computer model detection logic that replaces parts of the string from the PackageName property to retrieve the computer model only
2.1.6 - (2019-01-28) - Fixed an issue with the recurse injection of drivers for a single detected driver package that was using an unassigned variable
2.1.7 - (2019-02-13) - Added support for Windows 10 version 1809 in the Get-OSDetails function
2.1.8 - (2019-02-13) - Added trimming of manufacturer and models data gathering from WMI
2.1.9 - (2019-03-06) - Added support for non-terminating error when no matching driver packages where detected for OSUpgrade and DriverUpdate deployment types
2.2.0 - (2019-03-08) - Fixed an issue when attempting to run the script with -DebugMode switch that would cause it to break when it couldn't load the TS environment
2.2.1 - (2019-03-29) - New deployment type named 'PreCache' that allows the script to run in a pre-caching mode in a content pre-cache task sequence. When this deployment type is used, content will only be downloaded if it doesn't already
exist in the CCMCache. New parameter OperationalMode (defaults to Production) for better handling driver packages set for Pilot or Production deployment.
2.2.2 - (2019-05-14) - Improved the Surface model detection from WMI
2.2.3 - (2019-05-14) - Fixed an issue when multiple matching driver packages for a given model would only attempt to format the computer model name correctly for HP computers
2.2.4 - (2019-08-09) - Fixed an issue on OperationalMode Production to filter out pilot and retired packages
2.2.5 - (2019-12-02) - Added support for Windows 10 1903, 1909 and additional matching for Microsoft Surface devices (DAT 6.4.0 or neweer)
2.2.6 - (2020-02-06) - Fixed an issue where the single driver injection mode for BareMetal deployments would fail if there was a space in the driver inf name
2.2.7 - (2020-02-10) - Added a new parameter named TargetOSVersion. Use this parameter when DeploymentType is OSUpgrade and you don't want to rely on the OS version detected from the imported Operating System Upgrade Package or Operating System Image objects.
This parameter should mainly be used as an override and was implemented due to drivers for Windows 10 1903 were incorrectly detected when deploying or upgrading to Windows 10 1909 using imported source files, not for a
reference image for Windows 10 1909 as the Enablement Package would have flipped the build change to 18363 in such an image.
3.0.0 - (2020-03-14) - A complete re-written version of the script. Includes a much improved logging functionality. Script is now divided into phases, which are represented in the ApplyDriverPackage.log that will provide a better troubleshooting experience.
Added support for AZW and Fujitsu computer manufacturer by request from the community. Extended DebugMode to allow for overriding computer details, which allows the script to be tested against any model and it doesn't require to be tested
directly on the model itself.
3.0.1 - (2020-03-25) - Added TargetOSVersion parameter to be allowed to used in DebugMode.
- Fixed an issue where DebugMode would not be allowed to run on virtual machines. Fixed an issue where ComputerDetectionMethod script variable would be set to ComputerModel from
SystemSKU in case it couldn't match on the first driver package, leading to HP driver packages would always fail since they barely never match on the ComputerModel (they include 'Base Model', 'Notebook PC' etc.)
3.0.2 - (2020-03-29) - Fixed a spelling mistake in the Manufacturer parameter.
3.0.3 - (2020-03-31) - Small update to the Filter parameter's default value, it's now 'Drivers' instead of 'Driver'. Also added '64 bits' and '32 bits' to the translation function for the OS architecture of the current running task sequence.
3.0.4 - (2020-04-09) - Changed the translation function for the OS architecture of the current running task sequence into using wildcard support instead of adding language specified values
3.0.5 - (2020-04-30) - Added 7-Zip self extracting exe support for compressed driver packages
4.0.0 - (2020-06-29) - IMPORTANT: From this version and onwards, usage of the ConfigMgr WebService has been deprecated. This version will only work with the built-in AdminService in ConfigMgr.
Removed the DeploymentType parameter and replaced each deployment type with it's own switch parameter, e.g. -BareMetal, -DriverUpdate etc. Additional new parameters have been added, including the requirements of pre-defined Task Sequence variables
that the script requires. For more information, please refer to the embedded examples of how to use this script or refer to the official documentation at https://www.msendpointmgr.com/modern-driver-management.
4.0.1 - (2020-07-24) - Fixed an issue where an improper variable name was used instead of $DriverPackageCompressedFile
4.0.2 - (2020-08-07) - Fixed an issue where the Confirm-SystemSKU function would cause the script to crash if the SystemSKU data was improperly conformed, e.g. with spaces as a delimiter or with duplicate entries
4.0.3 - (2020-08-28) - Fixed an issue where the script would fail in case the driver package was missing SystemSKU values
4.0.4 - (2020-09-10) - IMPORTANT: This update addresses a change in Driver Automation Tool version 6.4.9 that comes with a change in naming HP driver packages such as 'Drivers - HP EliteBook x360 1030 G2 Base Model - Windows 10 1909 x64' instead of Hewlett-Packard in the name.
Before changing to version 4.0.4 of this script, ensure Driver Automation Tool have been executed and all HP driver packages now reflect these changes.
- Added support for decompressing WIM driver packages.
4.0.5 - (2020-09-16) - Fixed an issue for driver package compressed WIM support where it could not mount the file as the location was not empty, thanks to @SuneThomsenDK for reporting this.
4.0.6 - (2020-10-11) - Improved the AdminServiceEndpointType detection logic to mainly use the 'InInternet' property from ClientInfo WMI class together with if any detected type of active MP candidate was detected.
4.0.7 - (2020-10-27) - Updated with support for Windows 10 version 2009.
4.0.8 - (2020-12-09) - Added new functionality to be able to read a custom Application ID URI, if the default of https://ConfigMgrService is not defined on the ServerApp.
4.0.9 - (2020-12-10) - Fixed default parameter set to "BareMetal"
4.1.0 - (2021-02-16) - Added support for new Windows 10 build version naming scheme, such as 20H2, 21H1 and so on.
4.1.1 - (2021-03-17) - Fixed issue with driver package detection logic where null value could cause a matched entry
4.1.2 - (2021-05-14) - Fixed bug for Driver Update process on 20H2
4.1.3 - (2021-05-28) - Added support for Windows 10 21H1
4.2.0 - (2022-03-05) - This release contains several new features and is the first release to support Windows 11:
- New mandatory parameter TargetOSName added to separate between Windows 10 or Windows 11
- Improved driver package matching output to show if the operating system name was a match or not
- Added support for Getac manufacturer
- Extended the SystemSKU unwanted character cleanup process to include null and whitespaces
- Fixed several issues related to the Fallback Driver Package functionality where old code was left behind from the webservice days
#>
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "BareMetal")]
param(
[parameter(Mandatory = $true, ParameterSetName = "BareMetal", HelpMessage = "Set the script to operate in 'BareMetal' deployment type mode.")]
[switch]$BareMetal,
[parameter(Mandatory = $true, ParameterSetName = "DriverUpdate", HelpMessage = "Set the script to operate in 'DriverUpdate' deployment type mode.")]
[switch]$DriverUpdate,
[parameter(Mandatory = $true, ParameterSetName = "OSUpgrade", HelpMessage = "Set the script to operate in 'OSUpgrade' deployment type mode.")]
[switch]$OSUpgrade,
[parameter(Mandatory = $true, ParameterSetName = "PreCache", HelpMessage = "Set the script to operate in 'PreCache' deployment type mode.")]
[switch]$PreCache,
[parameter(Mandatory = $true, ParameterSetName = "XMLPackage", HelpMessage = "Set the script to operate in 'XMLPackage' deployment type mode.")]
[switch]$XMLPackage,
[parameter(Mandatory = $true, ParameterSetName = "Debug", HelpMessage = "Set the script to operate in 'DebugMode' deployment type mode.")]
[switch]$DebugMode,
[parameter(Mandatory = $true, ParameterSetName = "BareMetal", HelpMessage = "Specify the internal fully qualified domain name of the server hosting the AdminService, e.g. CM01.domain.local.")]
[parameter(Mandatory = $true, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $true, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $true, ParameterSetName = "PreCache")]
[parameter(Mandatory = $true, ParameterSetName = "Debug")]
[ValidateNotNullOrEmpty()]
[string]$Endpoint,
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage", HelpMessage = "Specify the deployment type mode for XML based driver package deployments, e.g. 'BareMetal', 'OSUpdate', 'DriverUpdate', 'PreCache'.")]
[ValidateNotNullOrEmpty()]
[ValidateSet("BareMetal", "OSUpdate", "DriverUpdate", "PreCache")]
[string]$XMLDeploymentType = "BareMetal",
[parameter(Mandatory = $true, ParameterSetName = "Debug", HelpMessage = "Specify the service account user name used for authenticating against the AdminService endpoint.")]
[ValidateNotNullOrEmpty()]
[string]$UserName = "",
[parameter(Mandatory = $true, ParameterSetName = "Debug", HelpMessage = "Specify the service account password used for authenticating against the AdminService endpoint.")]
[ValidateNotNullOrEmpty()]
[string]$Password = "",
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Define a filter used when calling the AdminService to only return objects matching the filter.")]
[parameter(Mandatory = $false, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "Debug")]
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[string]$Filter = "Drivers",
[parameter(Mandatory = $true, ParameterSetName = "BareMetal", HelpMessage = "Define the value that will be used as the target operating system name e.g. 'Windows 10'.")]
[parameter(Mandatory = $true, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $true, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $true, ParameterSetName = "PreCache")]
[parameter(Mandatory = $true, ParameterSetName = "Debug")]
[parameter(Mandatory = $true, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Windows 11", "Windows11", "Win11", "W11", "Windows 10", "Windows10", "Win10", "W10")]
[string]$TargetOSName,
[parameter(Mandatory = $true, ParameterSetName = "BareMetal", HelpMessage = "Define the value that will be used as the target operating system version e.g. '2004'.")]
[parameter(Mandatory = $true, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $true, ParameterSetName = "PreCache")]
[parameter(Mandatory = $true, ParameterSetName = "Debug")]
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[ValidateSet("22H2", "21H2", "21H1", "20H2", "2004", "1909", "1903", "1809", "1803", "1709", "1703", "1607")]
[string]$TargetOSVersion,
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Define the value that will be used as the target operating system architecture e.g. 'x64'.")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "Debug")]
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[ValidateSet("x64", "x86")]
[string]$TargetOSArchitecture = "x64",
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Define the operational mode, either Production or Pilot, for when calling ConfigMgr WebService to only return objects matching the selected operational mode.")]
[parameter(Mandatory = $false, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "Debug")]
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Production", "Pilot")]
[string]$OperationalMode = "Production",
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Specify if the script is to be used with a driver fallback package when a driver package for SystemSKU or computer model could not be detected.")]
[parameter(Mandatory = $false, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "Debug")]
[switch]$UseDriverFallback,
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Specify whether to install drivers using DISM.exe with recurse option or spawn a new process for each driver.")]
[parameter(Mandatory = $false, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "XMLPackage")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Single", "Recurse")]
[string]$DriverInstallMode = "Recurse",
[parameter(Mandatory = $false, ParameterSetName = "PreCache", HelpMessage = "Specify a custom path for the PreCache directory, overriding the default CCMCache directory.")]
[ValidateNotNullOrEmpty()]
[string]$PreCachePath,
[parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected computer manufacturer when running in debug mode.")]
[ValidateNotNullOrEmpty()]
[ValidateSet("HP", "Hewlett-Packard", "Dell", "Lenovo", "Microsoft", "Fujitsu", "Panasonic", "Viglen", "AZW", "Getac")]
[string]$Manufacturer,
[parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected computer model when running in debug mode.")]
[ValidateNotNullOrEmpty()]
[string]$ComputerModel,
[parameter(Mandatory = $false, ParameterSetName = "Debug", HelpMessage = "Override the automatically detected SystemSKU when running in debug mode.")]
[ValidateNotNullOrEmpty()]
[string]$SystemSKU,
[parameter(Mandatory = $false, ParameterSetName = "BareMetal", HelpMessage = "Use this switch to check for drivers packages that matches earlier versions of Windows than what's specified as input for TargetOSVersion.")]
[parameter(Mandatory = $false, ParameterSetName = "DriverUpdate")]
[parameter(Mandatory = $false, ParameterSetName = "OSUpgrade")]
[parameter(Mandatory = $false, ParameterSetName = "PreCache")]
[parameter(Mandatory = $false, ParameterSetName = "Debug")]
[switch]$OSVersionFallback
)
Begin {
# Load Microsoft.SMS.TSEnvironment COM object
if ($PSCmdLet.ParameterSetName -notlike "Debug") {
try {
$TSEnvironment = New-Object -ComObject "Microsoft.SMS.TSEnvironment" -ErrorAction Stop
}
catch [System.Exception] {
Write-Warning -Message "Unable to construct Microsoft.SMS.TSEnvironment object"; exit
}
}
# Enable TLS 1.2 support for downloading modules from PSGallery
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}
Process {
# Set Log Path
switch ($PSCmdLet.ParameterSetName) {
"Debug" {
$LogsDirectory = Join-Path -Path $env:SystemRoot -ChildPath "Temp"
}
default {
$LogsDirectory = $Script:TSEnvironment.Value("_SMSTSLogPath")
}
}
# Functions
function Write-CMLogEntry {
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 = "ApplyDriverPackage.log"
)
# Determine log file location
$LogFilePath = Join-Path -Path $LogsDirectory -ChildPath $FileName
# Construct time stamp for log entry
if (-not (Test-Path -Path 'variable:global:TimezoneBias')) {
[string]$global:TimezoneBias = [System.TimeZoneInfo]::Local.GetUtcOffset((Get-Date)).TotalMinutes
if ($TimezoneBias -match "^-") {
$TimezoneBias = $TimezoneBias.Replace('-', '+')
}
else {
$TimezoneBias = '-' + $TimezoneBias
}
}
$Time = -join @((Get-Date -Format "HH:mm:ss.fff"), $TimezoneBias)
# 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=""ApplyDriverPackage"" 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 to ApplyDriverPackage.log file. Error message at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)"
}
}
function Invoke-Executable {
param(
[parameter(Mandatory = $true, HelpMessage = "Specify the file name or path of the executable to be invoked, including the extension")]
[ValidateNotNullOrEmpty()]
[string]$FilePath,
[parameter(Mandatory = $false, HelpMessage = "Specify arguments that will be passed to the executable")]
[ValidateNotNull()]
[string]$Arguments
)
# Construct a hash-table for default parameter splatting
$SplatArgs = @{
FilePath = $FilePath
NoNewWindow = $true
Passthru = $true
ErrorAction = "Stop"
}
# Add ArgumentList param if present
if (-not([System.String]::IsNullOrEmpty($Arguments))) {
$SplatArgs.Add("ArgumentList", $Arguments)
}
# Invoke executable and wait for process to exit
try {
$Invocation = Start-Process @SplatArgs
$Handle = $Invocation.Handle
$Invocation.WaitForExit()
}
catch [System.Exception] {
Write-Warning -Message $_.Exception.Message; break
}
return $Invocation.ExitCode
}
function Invoke-CMDownloadContent {
param(
[parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Specify a PackageID that will be downloaded.")]
[Parameter(ParameterSetName = "CustomPath")]
[ValidateNotNullOrEmpty()]
[ValidatePattern("^[A-Z0-9]{3}[A-F0-9]{5}$")]
[string]$PackageID,
[parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Specify the download location type.")]
[Parameter(ParameterSetName = "CustomPath")]
[ValidateNotNullOrEmpty()]
[ValidateSet("Custom", "TSCache", "CCMCache")]
[string]$DestinationLocationType,
[parameter(Mandatory = $true, ParameterSetName = "NoPath", HelpMessage = "Save the download location to the specified variable name.")]
[Parameter(ParameterSetName = "CustomPath")]
[ValidateNotNullOrEmpty()]
[string]$DestinationVariableName,
[parameter(Mandatory = $true, ParameterSetName = "CustomPath", HelpMessage = "When location type is specified as Custom, specify the custom path.")]
[ValidateNotNullOrEmpty()]
[string]$CustomLocationPath
)
# Set OSDDownloadDownloadPackages
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDownloadPackages to: $($PackageID)" -Severity 1
$TSEnvironment.Value("OSDDownloadDownloadPackages") = "$($PackageID)"
# Set OSDDownloadDestinationLocationType
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationLocationType to: $($DestinationLocationType)" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationLocationType") = "$($DestinationLocationType)"
# Set OSDDownloadDestinationVariable
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationVariable to: $($DestinationVariableName)" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationVariable") = "$($DestinationVariableName)"
# Set OSDDownloadDestinationPath
if ($DestinationLocationType -like "Custom") {
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationPath to: $($CustomLocationPath)" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationPath") = "$($CustomLocationPath)"
}
# Set SMSTSDownloadRetryCount to 1000 to overcome potential BranchCache issue that will cause 'SendWinHttpRequest failed. 80072efe'
$TSEnvironment.Value("SMSTSDownloadRetryCount") = 1000
# Invoke download of package content
try {
if ($TSEnvironment.Value("_SMSTSInWinPE") -eq $false) {
Write-CMLogEntry -Value " - Starting package content download process (FullOS), this might take some time" -Severity 1
$ReturnCode = Invoke-Executable -FilePath (Join-Path -Path $env:windir -ChildPath "CCM\OSDDownloadContent.exe")
}
else {
Write-CMLogEntry -Value " - Starting package content download process (WinPE), this might take some time" -Severity 1
$ReturnCode = Invoke-Executable -FilePath "OSDDownloadContent.exe"
}
# Reset SMSTSDownloadRetryCount to 5 after attempted download
$TSEnvironment.Value("SMSTSDownloadRetryCount") = 5
# Match on return code
if ($ReturnCode -eq 0) {
Write-CMLogEntry -Value " - Successfully downloaded package content with PackageID: $($PackageID)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Failed to download package content with PackageID '$($PackageID)'. Return code was: $($ReturnCode)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
catch [System.Exception] {
Write-CMLogEntry -Value " - An error occurred while attempting to download package content. Error message: $($_.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
return $ReturnCode
}
function Invoke-CMResetDownloadContentVariables {
# Set OSDDownloadDownloadPackages
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDownloadPackages to a blank value" -Severity 1
$TSEnvironment.Value("OSDDownloadDownloadPackages") = [System.String]::Empty
# Set OSDDownloadDestinationLocationType
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationLocationType to a blank value" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationLocationType") = [System.String]::Empty
# Set OSDDownloadDestinationVariable
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationVariable to a blank value" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationVariable") = [System.String]::Empty
# Set OSDDownloadDestinationPath
Write-CMLogEntry -Value " - Setting task sequence variable OSDDownloadDestinationPath to a blank value" -Severity 1
$TSEnvironment.Value("OSDDownloadDestinationPath") = [System.String]::Empty
}
function New-TerminatingErrorRecord {
param(
[parameter(Mandatory = $false, HelpMessage = "Specify the exception message details.")]
[ValidateNotNullOrEmpty()]
[string]$Message = "InnerTerminatingFailure",
[parameter(Mandatory = $false, HelpMessage = "Specify the violation exception causing the error.")]
[ValidateNotNullOrEmpty()]
[string]$Exception = "System.Management.Automation.RuntimeException",
[parameter(Mandatory = $false, HelpMessage = "Specify the error category of the exception causing the error.")]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.ErrorCategory]$ErrorCategory = [System.Management.Automation.ErrorCategory]::NotImplemented,
[parameter(Mandatory = $false, HelpMessage = "Specify the target object causing the error.")]
[ValidateNotNullOrEmpty()]
[string]$TargetObject = ([string]::Empty)
)
# Construct new error record to be returned from function based on parameter inputs
$SystemException = New-Object -TypeName $Exception -ArgumentList $Message
$ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @($SystemException, $ErrorID, $ErrorCategory, $TargetObject)
# Handle return value
return $ErrorRecord
}
function Get-DeploymentType {
switch ($PSCmdlet.ParameterSetName) {
"XMLPackage" {
# Set required variables for XMLPackage parameter set
$Script:DeploymentMode = $Script:XMLDeploymentType
$Script:PackageSource = "XML Package Logic file"
# Define the path for the pre-downloaded XML Package Logic file called DriverPackages.xml
$script:XMLPackageLogicFile = (Join-Path -Path $TSEnvironment.Value("MDMXMLPackage01") -ChildPath "DriverPackages.xml")
if (-not (Test-Path -Path $XMLPackageLogicFile)) {
Write-CMLogEntry -Value " - Failed to locate required 'DriverPackages.xml' logic file for XMLPackage deployment type, ensure it has been pre-downloaded in a Download Package Content step before running this script" -Severity 3
# Throw terminating error $PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
default {
$Script:DeploymentMode = $Script:PSCmdlet.ParameterSetName
$Script:PackageSource = "AdminService"
}
}
}
function ConvertTo-ObfuscatedUserName {
param(
[parameter(Mandatory = $true, HelpMessage = "Specify the user name string to be obfuscated for log output.")]
[ValidateNotNullOrEmpty()]
[string]$InputObject
)
# Convert input object to a character array
$UserNameArray = $InputObject.ToCharArray()
# Loop through each character obfuscate every second item, with exceptions of the @ character if present
for ($i = 0; $i -lt $UserNameArray.Count; $i++) {
if ($UserNameArray[$i] -notmatch "@") {
if ($i % 2) {
$UserNameArray[$i] = "*"
}
}
}
# Join character array and return value
return -join @($UserNameArray)
}
function Test-AdminServiceData {
# Validate correct value have been either set as a TS environment variable or passed as parameter input for service account user name used to authenticate against the AdminService
if ([string]::IsNullOrEmpty($Script:UserName)) {
switch ($PSCmdLet.ParameterSetName) {
"Debug" {
Write-CMLogEntry -Value " - Required service account user name could not be determined from parameter input" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
default {
# Attempt to read TSEnvironment variable MDMUserName
$Script:UserName = $TSEnvironment.Value("MDMUserName")
if (-not ([string]::IsNullOrEmpty($Script:UserName))) {
# Obfuscate user name
$ObfuscatedUserName = ConvertTo-ObfuscatedUserName -InputObject $Script:UserName
Write-CMLogEntry -Value " - Successfully read service account user name from TS environment variable 'MDMUserName': $($ObfuscatedUserName)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Required service account user name could not be determined from TS environment variable" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
}
else {
# Obfuscate user name
$ObfuscatedUserName = ConvertTo-ObfuscatedUserName -InputObject $Script:UserName
Write-CMLogEntry -Value " - Successfully read service account user name from parameter input: $($ObfuscatedUserName)" -Severity 1
}
# Validate correct value have been either set as a TS environment variable or passed as parameter input for service account password used to authenticate against the AdminService
if ([string]::IsNullOrEmpty($Script:Password)) {
switch ($Script:PSCmdLet.ParameterSetName) {
"Debug" {
Write-CMLogEntry -Value " - Required service account password could not be determined from parameter input" -Severity 3
}
default {
# Attempt to read TSEnvironment variable MDMPassword
$Script:Password = $TSEnvironment.Value("MDMPassword")
if (-not([string]::IsNullOrEmpty($Script:Password))) {
Write-CMLogEntry -Value " - Successfully read service account password from TS environment variable 'MDMPassword': ********" -Severity 1
}
else {
Write-CMLogEntry -Value " - Required service account password could not be determined from TS environment variable" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
}
else {
Write-CMLogEntry -Value " - Successfully read service account password from parameter input: ********" -Severity 1
}
# Validate that if determined AdminService endpoint type is external, that additional required TS environment variables are available
if ($Script:AdminServiceEndpointType -like "External") {
if ($Script:PSCmdLet.ParameterSetName -notlike "Debug") {
# Attempt to read TSEnvironment variable MDMExternalEndpoint
$Script:ExternalEndpoint = $TSEnvironment.Value("MDMExternalEndpoint")
if (-not([string]::IsNullOrEmpty($Script:ExternalEndpoint))) {
Write-CMLogEntry -Value " - Successfully read external endpoint address for AdminService through CMG from TS environment variable 'MDMExternalEndpoint': $($Script:ExternalEndpoint)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Required external endpoint address for AdminService through CMG could not be determined from TS environment variable" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
# Attempt to read TSEnvironment variable MDMClientID
$Script:ClientID = $TSEnvironment.Value("MDMClientID")
if (-not([string]::IsNullOrEmpty($Script:ClientID))) {
Write-CMLogEntry -Value " - Successfully read client identification for AdminService through CMG from TS environment variable 'MDMClientID': $($Script:ClientID)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Required client identification for AdminService through CMG could not be determined from TS environment variable" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
# Attempt to read TSEnvironment variable MDMTenantName
$Script:TenantName = $TSEnvironment.Value("MDMTenantName")
if (-not([string]::IsNullOrEmpty($Script:TenantName))) {
Write-CMLogEntry -Value " - Successfully read client identification for AdminService through CMG from TS environment variable 'MDMTenantName': $($Script:TenantName)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Required client identification for AdminService through CMG could not be determined from TS environment variable" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
# Attempt to read TSEnvironment variable MDMApplicationIDURI
$Script:ApplicationIDURI = $TSEnvironment.Value("MDMApplicationIDURI")
if (-not([string]::IsNullOrEmpty($Script:ApplicationIDURI))) {
Write-CMLogEntry -Value " - Successfully read Application ID URI from TS environment variable 'MDMApplicationIDURI': $($Script:ApplicationIDURI)" -Severity 1
}
else {
Write-CMLogEntry -Value " - Using standard Application ID URI value: https://ConfigMgrService" -Severity 2
$Script:ApplicationIDURI = "https://ConfigMgrService"
}
}
}
}
function Get-AdminServiceEndpointType {
switch ($Script:DeploymentMode) {
"BareMetal" {
$SMSInWinPE = $TSEnvironment.Value("_SMSTSInWinPE")
if ($SMSInWinPE -eq $true) {
Write-CMLogEntry -Value " - Detected that script was running within a task sequence in WinPE phase, automatically configuring AdminService endpoint type" -Severity 1
$Script:AdminServiceEndpointType = "Internal"
}
else {
Write-CMLogEntry -Value " - Detected that script was not running in WinPE of a bare metal deployment type, this is not a supported scenario" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
"Debug" {
$Script:AdminServiceEndpointType = "Internal"
}
default {
Write-CMLogEntry -Value " - Attempting to determine AdminService endpoint type based on current active Management Point candidates and from ClientInfo class" -Severity 1
# Determine active MP candidates and if
$ActiveMPCandidates = Get-WmiObject -Namespace "root\ccm\LocationServices" -Class "SMS_ActiveMPCandidate"
$ActiveMPInternalCandidatesCount = ($ActiveMPCandidates | Where-Object {
$PSItem.Type -like "Assigned"
} | Measure-Object).Count
$ActiveMPExternalCandidatesCount = ($ActiveMPCandidates | Where-Object {
$PSItem.Type -like "Internet"
} | Measure-Object).Count
# Determine if ConfigMgr client has detected if the computer is currently on internet or intranet
$CMClientInfo = Get-WmiObject -Namespace "root\ccm" -Class "ClientInfo"
switch ($CMClientInfo.InInternet) {
$true {
if ($ActiveMPExternalCandidatesCount -ge 1) {
$Script:AdminServiceEndpointType = "External"
}
else {
Write-CMLogEntry -Value " - Detected as an Internet client but unable to determine External AdminService endpoint, bailing out" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
$false {
if ($ActiveMPInternalCandidatesCount -ge 1) {
$Script:AdminServiceEndpointType = "Internal"
}
else {
Write-CMLogEntry -Value " - Detected as an Intranet client but unable to determine Internal AdminService endpoint, bailing out" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
}
}
Write-CMLogEntry -Value " - Determined AdminService endpoint type as: $($AdminServiceEndpointType)" -Severity 1
}
function Set-AdminServiceEndpointURL {
switch ($Script:AdminServiceEndpointType) {
"Internal" {
$Script:AdminServiceURL = "https://{0}/AdminService/wmi" -f $Endpoint
}
"External" {
$Script:AdminServiceURL = "{0}/wmi" -f $ExternalEndpoint
}
}
Write-CMLogEntry -Value " - Setting 'AdminServiceURL' variable to: $($Script:AdminServiceURL)" -Severity 1
}
function Install-AuthModule {
# Determine if the PSIntuneAuth module needs to be installed
try {
Write-CMLogEntry -Value " - Attempting to locate PSIntuneAuth module" -Severity 1
$PSIntuneAuthModule = Get-InstalledModule -Name "PSIntuneAuth" -ErrorAction Stop -Verbose:$false
if ($PSIntuneAuthModule -ne $null) {
Write-CMLogEntry -Value " - Authentication module detected, checking for latest version" -Severity 1
$LatestModuleVersion = (Find-Module -Name "PSIntuneAuth" -ErrorAction SilentlyContinue -Verbose:$false).Version
if ($LatestModuleVersion -gt $PSIntuneAuthModule.Version) {
Write-CMLogEntry -Value " - Latest version of PSIntuneAuth module is not installed, attempting to install: $($LatestModuleVersion.ToString())" -Severity 1
$UpdateModuleInvocation = Update-Module -Name "PSIntuneAuth" -Scope CurrentUser -Force -ErrorAction Stop -Confirm:$false -Verbose:$false
}
}
}
catch [System.Exception] {
Write-CMLogEntry -Value " - Unable to detect PSIntuneAuth module, attempting to install from PSGallery" -Severity 2
try {
# Install NuGet package provider
$PackageProvider = Install-PackageProvider -Name "NuGet" -Force -Verbose:$false
# Install PSIntuneAuth module
Install-Module -Name "PSIntuneAuth" -Scope AllUsers -Force -ErrorAction Stop -Confirm:$false -Verbose:$false
Write-CMLogEntry -Value " - Successfully installed PSIntuneAuth module" -Severity 1
}
catch [System.Exception] {
Write-CMLogEntry -Value " - An error occurred while attempting to install PSIntuneAuth module. Error message: $($_.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
function Get-AuthToken {
try {
# Attempt to install PSIntuneAuth module, if already installed ensure the latest version is being used
Install-AuthModule
# Retrieve authentication token
Write-CMLogEntry -Value " - Attempting to retrieve authentication token using native client with ID: $($ClientID)" -Severity 1
$Script:AuthToken = Get-MSIntuneAuthToken -TenantName $TenantName -ClientID $ClientID -Credential $Credential -Resource $ApplicationIDURI -RedirectUri "https://login.microsoftonline.com/common/oauth2/nativeclient" -ErrorAction Stop
Write-CMLogEntry -Value " - Successfully retrieved authentication token" -Severity 1
}
catch [System.Exception] {
Write-CMLogEntry -Value " - Failed to retrieve authentication token. Error message: $($PSItem.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
function Get-AuthCredential {
# Construct PSCredential object for authentication
$EncryptedPassword = ConvertTo-SecureString -String $Script:Password -AsPlainText -Force
$Script:Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @($Script:UserName, $EncryptedPassword)
}
function Get-AdminServiceItem {
param(
[parameter(Mandatory = $true, HelpMessage = "Specify the resource for the AdminService API call, e.g. '/SMS_Package'.")]
[ValidateNotNullOrEmpty()]
[string]$Resource
)
# Construct array object to hold return value
$PackageArray = New-Object -TypeName System.Collections.ArrayList
switch ($Script:AdminServiceEndpointType) {
"External" {
try {
$AdminServiceUri = $AdminServiceURL + $Resource
Write-CMLogEntry -Value " - Calling AdminService endpoint with URI: $($AdminServiceUri)" -Severity 1
$AdminServiceResponse = Invoke-RestMethod -Method Get -Uri $AdminServiceUri -Headers $AuthToken -ErrorAction Stop
}
catch [System.Exception] {
Write-CMLogEntry -Value " - Failed to retrieve available package items from AdminService endpoint. Error message: $($PSItem.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
"Internal" {
$AdminServiceUri = $AdminServiceURL + $Resource
Write-CMLogEntry -Value " - Calling AdminService endpoint with URI: $($AdminServiceUri)" -Severity 1
try {
# Call AdminService endpoint to retrieve package data
$AdminServiceResponse = Invoke-RestMethod -Method Get -Uri $AdminServiceUri -Credential $Credential -ErrorAction Stop
}
catch [System.Security.Authentication.AuthenticationException] {
Write-CMLogEntry -Value " - The remote AdminService endpoint certificate is invalid according to the validation procedure. Error message: $($PSItem.Exception.Message)" -Severity 2
Write-CMLogEntry -Value " - Will attempt to set the current session to ignore self-signed certificates and retry AdminService endpoint connection" -Severity 2
# Attempt to ignore self-signed certificate binding for AdminService
# Convert encoded base64 string for ignore self-signed certificate validation functionality
$CertificationValidationCallbackEncoded = "DQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0AOwANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB1AHMAaQBuAGcAIABTAHkAcwB0AGUAbQAuAE4AZQB0ADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAZQBjAHUAcgBpAHQAeQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHUAcwBpAG4AZwAgAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBYADUAMAA5AEMAZQByAHQAaQBmAGkAYwBhAHQAZQBzADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcAB1AGIAbABpAGMAIABjAGwAYQBzAHMAIABTAGUAcgB2AGUAcgBDAGUAcgB0AGkAZgBpAGMAYQB0AGUAVgBhAGwAaQBkAGEAdABpAG8AbgBDAGEAbABsAGIAYQBjAGsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHAAdQBiAGwAaQBjACAAcwB0AGEAdABpAGMAIAB2AG8AaQBkACAASQBnAG4AbwByAGUAKAApAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAaQBmACgAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgAD0APQBuAHUAbABsACkADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgACsAPQAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAZABlAGwAZQBnAGEAdABlAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAATwBiAGoAZQBjAHQAIABvAGIAagAsACAADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAFgANQAwADkAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBlAHIAdABpAGYAaQBjAGEAdABlACwAIAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWAA1ADAAOQBDAGgAYQBpAG4AIABjAGgAYQBpAG4ALAAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABTAHMAbABQAG8AbABpAGMAeQBFAHIAcgBvAHIAcwAgAGUAcgByAG8AcgBzAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHIAZQB0AHUAcgBuACAAdAByAHUAZQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAA"
$CertificationValidationCallback = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($CertificationValidationCallbackEncoded))
# Load required type definition to be able to ignore self-signed certificate to circumvent issues with AdminService running with ConfigMgr self-signed certificate binding
Add-Type -TypeDefinition $CertificationValidationCallback
[ServerCertificateValidationCallback]::Ignore()
try {
# Call AdminService endpoint to retrieve package data
$AdminServiceResponse = Invoke-RestMethod -Method Get -Uri $AdminServiceUri -Credential $Credential -ErrorAction Stop
}
catch [System.Exception] {
Write-CMLogEntry -Value " - Failed to retrieve available package items from AdminService endpoint. Error message: $($PSItem.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
catch {
Write-CMLogEntry -Value " - Failed to retrieve available package items from AdminService endpoint. Error message: $($PSItem.Exception.Message)" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
# Add returned driver package objects to array list
if ($AdminServiceResponse.value -ne $null) {
foreach ($Package in $AdminServiceResponse.value) {
$PackageArray.Add($Package) | Out-Null
}
}
# Handle return value
return $PackageArray
}
function Get-OSImageDetails {
switch ($Script:DeploymentMode) {
"DriverUpdate" {
$OSImageDetails = [PSCustomObject]@{
Architecture = Get-OSArchitecture -InputObject (Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty OSArchitecture)
Name = $Script:TargetOSName
Version = Get-OSBuild -InputObject (Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty Version) -OSName $Script:TargetOSName
}
}
default {
$OSImageDetails = [PSCustomObject]@{
Architecture = $Script:TargetOSArchitecture
Name = $Script:TargetOSName
Version = $Script:TargetOSVersion
}
}
}
# Handle output to log file for OS image details
Write-CMLogEntry -Value " - Target operating system name configured as: $($OSImageDetails.Name)" -Severity 1
Write-CMLogEntry -Value " - Target operating system architecture configured as: $($OSImageDetails.Architecture)" -Severity 1
Write-CMLogEntry -Value " - Target operating system version configured as: $($OSImageDetails.Version)" -Severity 1
# Handle return value
return $OSImageDetails
}
function Get-OSBuild {
param(
[parameter(Mandatory = $true, HelpMessage = "OS version data to be translated.")]
[ValidateNotNullOrEmpty()]
[string]$InputObject,
[parameter(Mandatory = $true, HelpMessage = "OS name data to differentiate builds.")]
[ValidateNotNullOrEmpty()]
[string]$OSName
)
switch ($OSName) {
{"Windows 11", "Windows11", "Win11", "W11" -eq $_} {
switch (([System.Version]$InputObject).Build) {
"22000" {
$OSVersion = '21H2'
}
default {
Write-CMLogEntry -Value " - Unable to translate OS version using input object: $($InputObject)" -Severity 3
Write-CMLogEntry -Value " - Unsupported OS version detected, please reach out to the developers of this script" -Severity 3
# Throw terminating error
$PSCmdlet.ThrowTerminatingError((New-TerminatingErrorRecord))
}
}
}
{"Windows 10", "Windows10", "Win10", "W10" -eq $_} {
switch (([System.Version]$InputObject).Build) {
"19044" {
$OSVersion = '21H2'
}
"19043" {
$OSVersion = '21H1'
}
"19042" {
$OSVersion = '20H2'
}
"19041" {
$OSVersion = 2004
}
"18363" {
$OSVersion = 1909