-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathGet-IntuneManagementExtensionDiagnostics.ps1
5903 lines (4559 loc) · 240 KB
/
Get-IntuneManagementExtensionDiagnostics.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
<#PSScriptInfo
.VERSION 3.0
.GUID e0307766-d5a7-4704-a578-5ff1fb315a26
.AUTHOR [email protected]
.COMPANYNAME Yodamiitti Oy
.COPYRIGHT [email protected]
.TAGS Intune Windows Autopilot troubleshooting log analyzer
.LICENSEURI
.PROJECTURI https://github.com/petripaavola/Get-IntuneManagementExtensionDiagnostics
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
Version 1.0: Original published version
Version 1.1: Win32App and WinGetApp Required/Available and Install/Uninstall intent is detected right
Win32App Supersedence should be recognized (first uninstall and then install)
Win32App failed (un)install process is detected
Win32App Download Statistics table added
Added export to text files
Version 2.0: Huge new feature is to create html report
Html report is primary reporting and console observed timeline is secondary
All future development will be done to html report
Console timeline will be available for example for OOBE troubleshooting scenarios
Added App detection events to timeline
Html report entries support HoverOn ToolTips which include more information
Version 2.3: Updated script to use Microsoft.Graph.Authentication module to download data from Graph API
Version 2.4: Fix to process new log files AppWorkload.log
Version 3.0: Support for Intune Device Preparation.
Shows Remediation and PowerShell platform script contents and script output (if available) in (hover on) ToolTip with -Online option
Copy any ToolTip data including PowerShell scripts
-DoNotDownloadClearTextRemediationScriptsToReport - New option not to download Remediation script in clear text
#>
<#
.Synopsis
This script analyzes Microsoft Intune Management Extension (IME) log(s) and creates timeline report from found actions.
.DESCRIPTION
This script analyzes Microsoft Intune Management Extension (IME) log(s) and creates timeline report from found log events.
Report is saved to HTML file. Events are also shown in Powershell console window.
Timeline report includes information about Intune Win32App, WinGetApp, Powershell scripts, Remedation scripts and custom Compliance Policy scripts events. Windows Autopilot ESP phases are also shown on timeline.
Script also includes really capable Log Viewer UI if scripts is started with parameter -ShowLogViewerUI
LogViewerUI (Out-GridView) looks a lot like cmtrace.exe tool but it is better because all found log actions are added to log for easier debugging.
LogViewerUI has good search and filtering capabilities. Try to filter known log entries in Timeline: Add criteria -> ProcessRunTime -> is not empty.
What really differentiates this LogViewer from other tools is it's capability to convert GUIDs to known names
try parameter -ConvertAllKnownGuidsToClearText and you can see for example real application names instead of GUIDs on log events.
Selecting last line (RELOAD) and OK will reload log file.
Script can merge multiple log files so especially in LogViewerUI you can see Powershell command outputs from AgentExecutor.log
Powershell command outputs and errors can be also shown in Timeline view with parameters -ShowStdOutInReport and -ShowErrorsInReport
This shows instantly what is possible problem in Powershell scripts.
Possible Microsoft 365 App and MSI Line-of-Business Apps (maybe change to Win32App ;) installations are not seen by this report because they are not installed with Intune Management Agent.
Author:
Senior Modern Management Principal
Microsoft MVP - Windows and Intune
2024-09-17
https://github.com/petripaavola/Get-IntuneManagementExtensionDiagnostics
.PARAMETER Online
Download Powershell, Remediation and custom Compliance policy scripts to get displayName to Timeline report
Install Microsoft Graph module with command: Install-Module -Name Microsoft.Graph.Authentication -Scope CurrentUser
.PARAMETER LogFile
Specify log file fullpath
.PARAMETER LogFilesFolder
Specify folder where to check log files. Will show UI where you can select what logs to process
.PARAMETER LogStartDateTime
Specify date and time to start log entries. For example -
.PARAMETER LogEndDateTime
Specify date and time to stop log entries
.PARAMETER ShowLogViewerUI
Shows graphical LogViewerUI where all log events are easily browsed, searched and filtered in graphical UI
This parameter will always show file selection UI and event selection UI.
.PARAMETER LogViewerUI
Shows graphical LogViewerUI where all log events are easily browsed, searched and filtered in graphical UI
This parameter will always show file selection UI and event selection UI.
.PARAMETER AllLogEvents
Process all found log events.
Selecting this parameter will disable UI which asks date/time/hour selection for logs (use for silent commands or scripts)
This is default option (aka silent and no selection UI shown)
.PARAMETER LogEventsSelectionUI
Selecting this parameter will enable UI which asks date/time/hour selection for logs
.PARAMETER AllLogFiles
Process all found supported log file(s) automatically. This includes *AgentExecutor*.log, *IntuneManagementExtension*.log and *AppWorkload*.log
Selecting this parameter will disable UI which asks which log files to process (use for silent commands or scripts)
This is default option (aka silent and no selection UI shown)
.PARAMETER LogFilesSelectionUI
Selecting this parameter will enable UI which asks which log files to process
.PARAMETER Today
Show log entries from today (from midnight)
.PARAMETER ShowAllTimelineEvents
Shows more entries in report. This option will show starting messages for events which are not shown by default
.PARAMETER ShowStdOutInReport
Show script StdOut in events. This shows for example what Remediation script will return back to Intune
.PARAMETER ShowErrorsInReport
This will show found error messages from Powershell scripts. Note that Powershell script may succeed and still have errors shown here.
.PARAMETER ShowErrorsSummary
Show separate all errors summary after Timeline.
.PARAMETER ConvertAllKnownGuidsToClearText
This parameter replaces all known GUIDs to cleartext in LogViewerUI. Known GUIDs are Win32Apps and WinGetApps by default.
With -Online option also Powershell scripts, Proactive Remediation scripts and custom Compliance script will get name shown in UI.
Often this parameter helps a lot debugging log entries in LogViewerUI
.PARAMETER LongRunningPowershellNotifyThreshold
Threshold (seconds) after Timeline report will show warning message for long running Powershell scripts. Default value is 180 seconds.
.PARAMETER ExportTextFileName
Export Timeline information and possible Powershell script error to text file.
This expects either text filename or fullpath to textfile.
.PARAMETER FindAllLongRunningPowershellScripts
Poweruser option to try to find all long running Powershell scripts over threshold which default is 180 seconds
This option could find long running scripts which we don't even have event in our report.
.PARAMETER DoNotOpenReportAutomatically
Do not open html report file automatically in browser
.PARAMETER DoNotDownloadClearTextRemediationScriptsToReport
Do not download Remediation scripts in clear text to the report
This could be used for security measures.
Note that PowerShell script is always shown in report ToolTip in clear text because clear text PowerShell script is in log files
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -AllLogEvents -AllLogFiles
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -AllLogEvents -ShowAllTimelineEvents
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ShowLogViewerUI
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ShowLogViewerUI -ConvertAllKnownGuidsToClearText
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -Today
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -LogStartDateTime "10.3.2023 5.00:00" -LogEndDateTime "11.3.2023 23.00:00"
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log"
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -LogFile "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log"
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -LogFilesFolder "C:\temp\MDMDiagReport"
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ShowStdOutInReport
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ShowErrorsInReport
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ShowErrorsSummary
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ExportTextFileName ExportTextFile.txt
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -Online -ExportTextFileName C:\temp\ExportTextFile.txt
.EXAMPLE
.\Get-IntuneManagementExtensionDiagnostics.ps1 -AllLogEntries -AllLogFiles -ExportTextFileName C:\temp\ExportTextFile.txt
.EXAMPLE
Get-ChildItem "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log" | .\Get-IntuneManagementExtensionDiagnostics.ps1 -AllLogEntries -Online
.INPUTS
Script accepts File object as input. This would be same than specifying Parameter -LogFile
.OUTPUTS
None
.NOTES
You can download current version of this script from PowershellGallery with command
Save-Script Get-IntuneManagementExtensionDiagnostics -Path ./
.LINK
https://github.com/petripaavola/Get-IntuneManagementExtensionDiagnostics
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false,
HelpMessage = 'Enter Intune IME log file fullpath',
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[String]$LogFile = $null,
[Parameter(Mandatory=$false,
HelpMessage = 'Enter Intune IME log files folder path',
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false)]
[String]$LogFilesFolder = $null,
[Parameter(Mandatory=$false)]
[Switch]$Online,
[Parameter(Mandatory=$false,
HelpMessage = 'Enter Start DateTime for log entries (for example "10.3.2023 5:00:00")',
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false)]
$LogStartDateTime = $null,
[Parameter(Mandatory=$false,
HelpMessage = 'Enter End DateTime for log entries (for example "11.3.2023 23.00:00")',
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false)]
$LogEndDateTime = $null,
[Parameter(Mandatory=$false)]
[Switch]$ShowLogViewerUI,
[Parameter(Mandatory=$false)]
[Switch]$LogViewerUI,
[Parameter(Mandatory=$false)]
[Switch]$AllLogEntries=$true,
[Parameter(Mandatory=$false)]
[Switch]$LogEntriesSelectionUI,
[Parameter(Mandatory=$false)]
[Switch]$AllLogFiles=$true,
[Parameter(Mandatory=$false)]
[Switch]$LogFilesSelectionUI,
[Parameter(Mandatory=$false)]
[Switch]$Today,
[Parameter(Mandatory=$false)]
[Switch]$ShowAllTimelineEvents,
[Parameter(Mandatory=$false)]
[Switch]$ShowStdOutInReport,
[Parameter(Mandatory=$false)]
[Switch]$ShowErrorsInReport,
[Parameter(Mandatory=$false)]
[Switch]$ShowErrorsSummary,
[Parameter(Mandatory=$false)]
[Switch]$ConvertAllKnownGuidsToClearText,
[Parameter(Mandatory=$false,
HelpMessage = 'Threshold seconds to highlight long running Powershell scripts',
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[int]$LongRunningPowershellNotifyThreshold = 180,
[Parameter(Mandatory=$false,
HelpMessage = 'Enter text (.txt) filename to export info to',
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false)]
[String]$ExportTextFileName=$null,
[Parameter(Mandatory=$false)]
[String]$ExportHTMLReportPath=$null,
[Parameter(Mandatory=$false)]
[Switch]$FindAllLongRunningPowershellScripts,
[Parameter(Mandatory=$false)]
[Switch]$DoNotOpenReportAutomatically,
[Parameter(Mandatory=$false)]
[Switch]$DoNotDownloadClearTextRemediationScriptsToReport
)
$ScriptVersion = "3.0"
$TimeOutBetweenGraphAPIRequests = 300
Write-Host "Get-IntuneManagementExtensionDiagnostics.ps1 $ScriptVersion" -ForegroundColor Cyan
Write-Host "Author: [email protected] / Microsoft MVP - Windows and Intune"
Write-Host ""
$ExportHTML=$True
# Make script to not show start selection UIs by default
# This should be fixed in the code
# but this was quicker hack to make script silent by default
$AllLogEntries=$true
$AllLogFiles = $true
if($LogEntriesSelectionUI) {
$AllLogEntries=$false
}
if($LogFilesSelectionUI) {
$AllLogFiles = $false
}
# Show selection UIs with LogViewerUI because we would like to
# limit as less as events possible to save memory and speed up Out-GridView
# With LogViewerUI show Events selection UI always
# With LogViewerUI show file selection UI always
if($ShowLogViewerUI -or $LogViewerUI) {
$LogEntriesSelectionUI = $true
$LogFilesSelectionUI = $true
$AllLogEntries=$false
$AllLogFiles = $false
Write-Host "Parameter -ShowLogViewerUI selected. Script will show log file and event selection UIs."
}
# Set variables automatically if we are in Windows Autopilot ESP (Enrollment Status Page)
# Idea is that user can just run the script without checking Parameters first
if($env:UserName -eq 'defaultUser0') {
Write-Host "Detected running in Windows Autopilot Enrollment Status Page (ESP)" -ForegroundColor Yellow
#$LOGFile='C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log'
# Do not open HTML report to browser
$DoNotOpenReportAutomatically = $true
if((-not $LogFilesFolder) -or (-not $LOGFile)) {
Write-Host "Configuring parameters automatically"
Write-Host "Selected: All log files from default Intune IME logs folder"
Write-Host "Selected: Do not open HTML report automatically"
Write-Host
$LogFilesFolder = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
if(-not (Test-Path $LogFilesFolder)) {
Write-Host "Log folder does not exist yet: $LogFilesFolder"
Write-Host "Try again in a moment..." -ForegroundColor Yellow
Write-Host ""
Exit 0
}
}
# Save Computer Name
$ComputerNameForReport = $env:ComputerName
# Process all found supported log files
# This will not show file selection UI
$AllLogFiles=$True
# Process all log entries
# This will not show time selection UI
$AllLogEntries=$True
# Show all entries in Timeline
# This especially useful if some script or application hangs for a long time
# so then you can see start entry for that script or application and you know what is current running Intune deployment
$ShowAllTimelineEvents=$True
# Do not download Remediation scripts in clear text to Report
# Reason is that many may not event know that script did also html report
# In OOBE usually the console view is enough and html file may be left to the device disk
$DoNotDownloadClearTextRemediationScriptsToReport=$True
if(-not (Test-Path 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log')) {
Write-Host "Log file does not exist yet: C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log" -ForegroundColor Yellow
Write-Host "Try again in a moment..." -ForegroundColor Yellow
Write-Host ""
Exit 0
}
}
# Hashtable with ids and names
$IdHashtable = @{}
# Save timeline objects to this List
$observedTimeline = [System.Collections.Generic.List[PSObject]]@()
# Save application download statistics to this list
$ApplicationDownloadStatistics = [System.Collections.Generic.List[PSObject]]@()
# Save filtered oud applications to to this list
$ApplicationAssignmentFilterApplied = [System.Collections.Generic.List[PSObject]]@()
# TimeLine entry index
# This might be used in HTML table for sorting entries
$Script:observedTimeLineIndexToHTMLTable=0
################ Functions ################
# This is aligned with Michael Niehaus's Get-AutopilotDiagnostics script just in case
# region Functions
Function RecordStatusToTimeline {
param
(
[Parameter(Mandatory=$true)] [String] $date,
[Parameter(Mandatory=$true)] [String] $status,
[Parameter(Mandatory=$false)] [String] $type,
[Parameter(Mandatory=$false)] [String] $intent,
[Parameter(Mandatory=$false)] [String] $detail,
[Parameter(Mandatory=$false)] $seconds,
[Parameter(Mandatory=$false)] [String] $logEntry,
[Parameter(Mandatory=$false)] [String] $color,
[Parameter(Mandatory=$false)] [String] $DetailToolTip
)
$Script:observedTimeLineIndexToHTMLTable++
# Round seconds to full seconds
if($seconds) {
$seconds = [math]::Round($seconds)
}
$observedTimeline.add([PSCustomObject]@{
'Index' = $Script:observedTimeLineIndexToHTMLTable
'Date' = $date
'Status' = $status
'Type' = $type
'Intent' = $intent
'Detail' = $detail
'Seconds' = $seconds
'LogEntry' = $logEntry
'Color' = $color
'DetailToolTip' = $DetailToolTip
})
}
Function Get-AppIntent {
Param(
$AppId
)
$intent = 'Unknown Intent'
if($AppId) {
if($IdHashtable.ContainsKey($AppId)) {
$AppPolicy=$IdHashtable[$AppId]
if($AppPolicy.Intent) {
Switch ($AppPolicy.Intent)
{
0 { $intent = 'Not Targeted' }
1 { $intent = 'Available Install' }
3 { $intent = 'Required Install' }
4 { $intent = 'Required Uninstall' }
default { $intent = 'Unknown Intent' }
}
}
}
}
return $intent
}
Function Get-AppIntentNameForNumber {
Param(
$IntentNumber
)
Switch ($IntentNumber)
{
0 { $intent = 'Not Targeted' }
1 { $intent = 'Available Install' }
3 { $intent = 'Required Install' }
4 { $intent = 'Required Uninstall' }
default { $intent = 'Unknown Intent' }
}
return $intent
}
Function Get-AppDetectionResultForNumber {
Param(
$DetectionNumber
)
Switch ($DetectionNumber)
{
0 { $DetectionState = 'Unknown' }
1 { $DetectionState = 'Detected' }
2 { $DetectionState = 'Not Detected' }
3 { $DetectionState = 'Unknown' }
4 { $DetectionState = 'Unknown' }
5 { $DetectionState = 'Unknown' }
default { $DetectionState = 'Unknown' }
}
return $DetectionState
}
Function Get-AppName {
Param(
$AppId
)
$AppName = $null
if($AppId) {
if($IdHashtable.ContainsKey($AppId)) {
$AppPolicy=$IdHashtable[$AppId]
if($AppPolicy.Name) {
$AppName = $AppPolicy.Name
}
}
}
return $AppName
}
Function Get-AppType {
Param(
$AppId
)
$AppType = 'App'
if($AppId) {
if($IdHashtable.ContainsKey($AppId)) {
$AppPolicy=$IdHashtable[$AppId]
if($AppPolicy.InstallerData) {
# This should be New Store App
$AppType = 'WinGetApp'
} else {
# This should be Win32App
$AppType = 'Win32App'
}
}
}
return $AppType
}
Function Convert-AppDetectionValuesToHumanReadable {
Param(
$DetectionRulesObject
)
# Object has DetectionType and DetectionText objects
# Object is array of objects
<#
[
{
"DetectionType": 2,
"DetectionText": {
"Path": "C:\\Program Files (x86)\\Foo",
"FileOrFolderName": "bar.exe",
"Check32BitOn64System": true,
"DetectionType": 1,
"Operator": 0,
"DetectionValue": null
}
}
]
#>
foreach($DetectionRule in $DetectionRulesObject) {
# Change DetectionText properties values to text
# DetectionType: Registry
if($DetectionRule.DetectionType -eq 0) {
# Registry Detection Type values
# https://learn.microsoft.com/en-us/graph/api/resources/intune-apps-win32lobappregistrydetectiontype?view=graph-rest-beta
Switch ($DetectionRule.DetectionText.DetectionType) {
0 { $DetectionRule.DetectionText.DetectionType = 'Not configure' }
1 { $DetectionRule.DetectionText.DetectionType = 'Value exists' }
2 { $DetectionRule.DetectionText.DetectionType = 'Value does not exist' }
3 { $DetectionRule.DetectionText.DetectionType = 'String comparison' }
4 { $DetectionRule.DetectionText.DetectionType = 'Integer comparison' }
5 { $DetectionRule.DetectionText.DetectionType = 'Version comparison' }
}
# Registry Detection Operation values
# https://learn.microsoft.com/en-us/graph/api/resources/intune-apps-win32lobappruleoperator?view=graph-rest-beta
Switch ($DetectionRule.DetectionText.Operator) {
0 { $DetectionRule.DetectionText.Operator = 'Not configured' }
1 { $DetectionRule.DetectionText.Operator = 'Equals' }
2 { $DetectionRule.DetectionText.Operator = 'Not equal to' }
4 { $DetectionRule.DetectionText.Operator = 'Greater than' }
5 { $DetectionRule.DetectionText.Operator = 'Greater than or equal to' }
8 { $DetectionRule.DetectionText.Operator = 'Less than' }
9 { $DetectionRule.DetectionText.Operator = 'Less than or equal to' }
}
}
# DetectionType: File
if($DetectionRule.DetectionType -eq 2) {
# File Detection Type values
# https://learn.microsoft.com/en-us/graph/api/resources/intune-apps-win32lobappfilesystemdetectiontype?view=graph-rest-beta
Switch ($DetectionRule.DetectionText.DetectionType) {
0 { $DetectionRule.DetectionText.DetectionType = 'Not configure' }
1 { $DetectionRule.DetectionText.DetectionType = 'File or folder exists' }
2 { $DetectionRule.DetectionText.DetectionType = 'Date modified' }
3 { $DetectionRule.DetectionText.DetectionType = 'Date created' }
4 { $DetectionRule.DetectionText.DetectionType = 'String (version)' }
5 { $DetectionRule.DetectionText.DetectionType = 'Size in MB' }
6 { $DetectionRule.DetectionText.DetectionType = 'File or folder does not exist' }
}
# File Detection Operator values
# https://learn.microsoft.com/en-us/graph/api/resources/intune-apps-win32lobappdetectionoperator?view=graph-rest-beta
Switch ($DetectionRule.DetectionText.Operator) {
0 { $DetectionRule.DetectionText.Operator = 'Not configured' }
1 { $DetectionRule.DetectionText.Operator = 'Equals' }
2 { $DetectionRule.DetectionText.Operator = 'Not equal to' }
4 { $DetectionRule.DetectionText.Operator = 'Greater than' }
5 { $DetectionRule.DetectionText.Operator = 'Greater than or equal to' }
8 { $DetectionRule.DetectionText.Operator = 'Less than' }
9 { $DetectionRule.DetectionText.Operator = 'Less than or equal to' }
}
}
# DetectionType: Custom script
if($DetectionRule.DetectionType -eq 3) {
# Convert base64 script to clear text
#$DetectionRule.DetectionText.ScriptBody
# Decode Base64 content
$b = [System.Convert]::FromBase64String("$($DetectionRule.DetectionText.ScriptBody)")
$DetectionRule.DetectionText.ScriptBody = [System.Text.Encoding]::UTF8.GetString($b)
}
<#
# Change DetectionType value to text
Switch ($DetectionRule.DetectionType) {
0 { $DetectionRule.DetectionType = 'Registry' }
1 { $DetectionRule.DetectionType = 'MSI' }
2 { $DetectionRule.DetectionType = 'File' }
3 { $DetectionRule.DetectionType = 'Custom script' }
default { $DetectionRule.DetectionType = $DetectionRule.DetectionType }
}
#>
# Add new property with DetectionType value as text
Switch ($DetectionRule.DetectionType) {
0 { $DetectionRule | Add-Member -MemberType noteProperty -Name DetectionTypeAsText -Value 'Registry' }
1 { $DetectionRule | Add-Member -MemberType noteProperty -Name DetectionTypeAsText -Value 'MSI' }
2 { $DetectionRule | Add-Member -MemberType noteProperty -Name DetectionTypeAsText -Value 'File' }
3 { $DetectionRule | Add-Member -MemberType noteProperty -Name DetectionTypeAsText -Value 'Custom script' }
default { $DetectionRule | Add-Member -MemberType noteProperty -Name DetectionTypeAsText -Value $DetectionRule.DetectionType }
}
}
return $DetectionRulesObject
}
function Invoke-MgGraphRequestGetAllPages {
param (
[Parameter(Mandatory = $true)]
[String]$uri
)
$MgGraphRequest = $null
$AllMSGraphRequest = $null
Start-Sleep -Milliseconds $TimeOutBetweenGraphAPIRequests
try {
# Save results to this variable
$allGraphAPIData = @()
do {
$MgGraphRequest = $null
$MgGraphRequest = Invoke-MgGraphRequest -Uri $uri -Method 'Get' -OutputType PSObject -ContentType "application/json"
if($MgGraphRequest) {
# Test if object has attribute named Value (whether value is null or not)
#if((Get-Member -inputobject $MgGraphRequest -name 'Value' -Membertype Properties) -and (Get-Member -inputobject $MgGraphRequest -name '@odata.context' -Membertype Properties)) {
if(Get-Member -inputobject $MgGraphRequest -name 'Value' -Membertype Properties) {
# Value property exists
$allGraphAPIData += $MgGraphRequest.Value
# Check if we have value starting https:// in attribute @odate.nextLink
# and check that $Top= parameter was NOT used. With $Top= parameter we can limit search results
# but that almost always results .nextLink being present if there is more data than specified with top
# If we specified $Top= ourselves then we don't want to fetch nextLink values
#
# So get GraphAllPages if there is valid nextlink and $Top= was NOT used in url originally
if (($MgGraphRequest.'@odata.nextLink' -like 'https://*') -and (-not ($uri.Contains('$top=')))) {
# Save nextLink url to variable and rerun do-loop
$uri = $MgGraphRequest.'@odata.nextLink'
Start-Sleep -Milliseconds $TimeOutBetweenGraphAPIRequests
# Continue to next round in Do-loop
Continue
} else {
# We dont have nextLink value OR
# $top= exists so we return what we got from first round
#return $allGraphAPIData
$uri = $null
}
} else {
# Sometimes we get results without Value-attribute (eg. getting user details)
# We will return all we got as is
# because there should not be nextLink page in this case ???
return $MgGraphRequest
}
} else {
# Invoke-MGGraphRequest failed so we return false
return $null
}
} while ($uri) # Always run once and continue if there is nextLink value
# We should not end here but just in case
return $allGraphAPIData
} catch {
Write-Error "There was error with MGGraphRequest with url $url!"
return $null
}
}
function Get-IntunePowershellScriptContentInCleartext {
Param(
[Parameter(Mandatory=$true)]
[String]$PowershellScriptPolicyId
)
# Check if we already have value
if($IdHashtable[$PowershellScriptPolicyId].scriptContentClearText) {
# Powershell script in clear text already exists in HashTable object
# So we can return it
return $IdHashtable[$PowershellScriptPolicyId].scriptContentClearText
} else {
# Property scriptContentClearText does NOT exist in HashTable object
# Download Powershell script in cleartext if -Online parameter has been specified
# PowerShell script is in clear text in IME log files so this is always shown
# And later version might get PowerShell script from IME log instead from Graph
if($Online) {
#Write-Verbose "Downloading Powershell script: $PowershellScriptPolicyId"
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/$PowershellScriptPolicyId"
$IntunePowershellScriptInformation = Invoke-MgGraphRequestGetAllPages -Uri $uri
if($IntunePowershellScriptInformation) {
#Write-Verbose "Done" -ForegroundColor Green
if($IntunePowershellScriptInformation.scriptContent) {
Try {
# Convert Intune Powershell script base64 content to clear text
$b = [System.Convert]::FromBase64String("$($IntunePowershellScriptInformation.scriptContent)")
$IntuneScriptContentInClearText = [System.Text.Encoding]::UTF8.GetString($b)
} catch {
# Some fatal error converting base64 to cleartext
Write-Error "Error converting Intune Powershell script base64 to cleartext" -ForegroundColor Red
return 'N/A'
}
# Add new property to HashTable object if it doesn't already exist
if(-not $IdHashtable[$PowershellScriptPolicyId].scriptContentClearText) {
$IdHashtable[$PowershellScriptPolicyId] | Add-Member -MemberType noteProperty -Name scriptContentClearText -Value $IntuneScriptContentInClearText
} else {
$IdHashtable[$PowershellScriptPolicyId].scriptContentClearText = $IntuneScriptContentInClearText
}
return $IntuneScriptContentInClearText
} else {
# Did not get scriptContent information
# We should never get here if we got anything successfully from Graph API
Write-Verbose "Failed to download Powershell scriptContent property" -ForegroundColor Yellow
return 'N/A'
}
} else {
# Could not get Intune Powershell information from Intune
# Doing nothing
Write-Host "Failed to download Powershell script from Intune" -ForegroundColor Yellow
return 'N/A'
}
} else {
# -Online not selected and we didn't have value so return $null
return 'N/A'
}
}
# We should not get here
return 'N/A'
}
function Get-IntuneRemediationDetectionScriptContentInCleartext {
Param(
[Parameter(Mandatory=$true)]
[String]$ScriptPolicyId
)
# Check if we already have value
if($IdHashtable[$ScriptPolicyId].detectionScriptContentClearText) {
# Powershell script in clear text already exists in HashTable object
# So we can return it
return $IdHashtable[$ScriptPolicyId].detectionScriptContentClearText
} else {
# Property detectionScriptContent does NOT exist in HashTable object
# Download Remediation Detection script in cleartext if -Online parameter has been specified
# and -DoNotDownloadClearTextRemediationScriptsToReport is NOT specified
if($Online -and (-not $DoNotDownloadClearTextRemediationScriptsToReport)) {
#Write-Verbose "Downloading Remediation Detection script: $ScriptPolicyId"
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts/$ScriptPolicyId"
#Write-Verbose "URI: $uri"
$IntuneRemediationScriptInformation = Invoke-MgGraphRequestGetAllPages -Uri $uri
if($IntuneRemediationScriptInformation) {
#Write-Verbose "Done" -ForegroundColor Green
if($IntuneRemediationScriptInformation.detectionScriptContent) {
Try {
# Convert Intune Remediation Detection script base64 content to clear text
$b = [System.Convert]::FromBase64String("$($IntuneRemediationScriptInformation.detectionScriptContent)")
$IntuneScriptContentInClearText = [System.Text.Encoding]::UTF8.GetString($b)
} catch {
# Some fatal error converting base64 to cleartext
Write-Error "Error converting Intune Remediation Detection script base64 to cleartext" -ForegroundColor Red
return 'N/A'
}
# Add new property to HashTable object if it doesn't already exist
if(-not $IdHashtable[$ScriptPolicyId].detectionScriptContentClearText) {
$IdHashtable[$ScriptPolicyId] | Add-Member -MemberType noteProperty -Name detectionScriptContentClearText -Value $IntuneScriptContentInClearText
} else {
$IdHashtable[$PowershellScriptPolicyId].detectionScriptContentClearText = $IntuneScriptContentInClearText
}
return $IntuneScriptContentInClearText
} else {
# Did not get detectionScriptContent information
# We should never get here if we got anything successfully from Graph API
Write-Verbose "Failed to download Remediation Detect detectionScriptContent property" -ForegroundColor Yellow
return 'N/A'
}
} else {
# Could not get Intune Remediation Detect information from Intune
# Doing nothing
Write-Host "Failed to download Remediation Detect script from Intune" -ForegroundColor Yellow
return 'N/A'
}
} else {
# -Online not selected and we didn't have value so return $null
return 'N/A'
}
}
# We should not get here
return 'N/A'
}
function Get-IntuneRemediationRemediateScriptContentInCleartext {
Param(
[Parameter(Mandatory=$true)]
[String]$ScriptPolicyId
)
# Check if we already have value
if($IdHashtable[$ScriptPolicyId].RemediateScriptContentClearText) {
# Powershell script in clear text already exists in HashTable object
# So we can return it
return $IdHashtable[$ScriptPolicyId].RemediateScriptContentClearText
} else {
# Property remediationScriptContent does NOT exist in HashTable object
# Download Remediation Remediate script in cleartext if -Online parameter has been specified
# and -DoNotDownloadClearTextRemediationScriptsToReport is NOT specified
if($Online -and (-not $DoNotDownloadClearTextRemediationScriptsToReport)) {
#Write-Verbose "Downloading Remediation Remediate script: $ScriptPolicyId"
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts/$ScriptPolicyId"
$IntuneRemediationScriptInformation = Invoke-MgGraphRequestGetAllPages -Uri $uri
if($IntuneRemediationScriptInformation) {
#Write-Verbose "Done" -ForegroundColor Green
if($IntuneRemediationScriptInformation.remediationScriptContent) {
Try {
# Convert Intune Remediation Detection script base64 content to clear text
$b = [System.Convert]::FromBase64String("$($IntuneRemediationScriptInformation.remediationScriptContent)")
$IntuneScriptContentInClearText = [System.Text.Encoding]::UTF8.GetString($b)
} catch {
# Some fatal error converting base64 to cleartext
Write-Error "Error converting Intune Remediation Detection script base64 to cleartext" -ForegroundColor Red
return 'N/A'
}
# Add new property to HashTable object if it doesn't already exist
if(-not $IdHashtable[$ScriptPolicyId].RemediateScriptContentClearText) {
$IdHashtable[$ScriptPolicyId] | Add-Member -MemberType noteProperty -Name RemediateScriptContentClearText -Value $IntuneScriptContentInClearText
} else {
$IdHashtable[$PowershellScriptPolicyId].RemediateScriptContentClearText = $IntuneScriptContentInClearText
}
return $IntuneScriptContentInClearText
} else {
# Did not get remediationScriptContent information
# We should never get here if we got anything successfully from Graph API
Write-Verbose "Failed to download Powershell remediationScriptContent property" -ForegroundColor Yellow
return 'N/A'
}
} else {
# Could not get Intune Remediation script information from Intune
# Doing nothing
Write-Host "Failed to download Remediation Remediate script from Intune" -ForegroundColor Yellow
return 'N/A'
}
} else {
# -Online not selected and we didn't have value so return $null
return 'N/A'
}
}
# We should not get here
return 'N/A'
}
function Get-IntuneCustomComplianceScriptContentInCleartext {
Param(
[Parameter(Mandatory=$true)]
[String]$ScriptPolicyId
)
# Check if we already have value
if($IdHashtable[$ScriptPolicyId].detectionScriptContentClearText) {
# Powershell script in clear text already exists in HashTable object
# So we can return it
return $IdHashtable[$ScriptPolicyId].detectionScriptContentClearText
} else {
# Property detectionScriptContent does NOT exist in HashTable object
# Download Custom Compliance script in cleartext if -Online parameter has been specified
# and -DoNotDownloadClearTextRemediationScriptsToReport is NOT specified
if($Online -and (-not $DoNotDownloadClearTextRemediationScriptsToReport)) {
#Write-Verbose "Downloading Custom Compliance script: $ScriptPolicyId"
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceComplianceScripts/$ScriptPolicyId"
$IntuneRemediationScriptInformation = Invoke-MgGraphRequestGetAllPages -Uri $uri
if($IntuneRemediationScriptInformation) {
#Write-Verbose "Done" -ForegroundColor Green
if($IntuneRemediationScriptInformation.detectionScriptContent) {
Try {
# Convert Intune Custom Compliance script base64 content to clear text
$b = [System.Convert]::FromBase64String("$($IntuneRemediationScriptInformation.detectionScriptContent)")