├── Applocker
├── Create_Applocker_Intune_policy.ps1
└── README.md
├── Apps
├── Audacity_3.5.1_Intune_Win32_Custom_DetectionCheck_Script.ps1
├── Create_Multiple_IntuneMobileAppAssignments_Out-GridView.ps1
├── Delete_Multiple_IntuneMobileAppAssignments_Out-GridView.ps1
├── README.md
├── Show_IntuneMobileAppAssignments_to_selected_AzureADGroup_in_Out-GridView.ps1
├── install_Audacity_3.5.1-EXE-example.ps1
└── install_LibreOffice_7.0.6-MSI-example.ps1
├── AutopilotBranding-Yodamiitti
└── README.md
├── CVE-2023-24932
├── BlackLotus-Intune-Remediation-screenshot.png
├── Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-1of3-Check if new 2023 certificate is installed to UEFI db.ps1
├── Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-2of3-Check if new 2023 certificate is installed to Windows Boot Manager.ps1
├── Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-3of3-Check if old compromised 2011 certificate is revocaked in UEFI.ps1
└── readme.md
├── Device_Query
└── readme.MD
├── Devices
├── Intune_Device_SendCustomNotification.ps1
└── README.md
├── Groups
└── README.md
├── Linux
├── Intune_Linux_Custom_Compliance_script.sh
├── Intune_Linux_Custom_Compliance_script_Rules_file.json
├── README.md
└── pics
│ ├── IntuneAddCustomCompliancePolicySelectFiles.png
│ ├── IntuneAddCustomCompliancePolicySettingsPicker.png
│ ├── IntuneAddLinuxCustomComplianceScript.png
│ ├── Intune_Linux_Compliance_Policy.png
│ ├── Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant.png
│ ├── Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant_small.png
│ ├── Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant.png
│ └── Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant_small.png
├── Microsoft_Graph_API
├── AppRegistrations_Secret_Expiry_Report_v1.ps1
├── AppRegistrations_Secret_Expiry_Report_v2.ps1
├── AppRegistrations_Secret_Expiry_Report_v3_Using_Certificate.ps1
├── AppRegistrations_Secret_Expiry_Report_v6_Using_AppSecret_and_Intune_PowershellModule.ps1
├── Azure_Runbook_using_CredentialsVariable.ps1
├── CreateApplicationCertificate.ps1
├── Intune_Devices_Sync_Report_Report_v4_Using_Certificate.ps1
├── Intune_Devices_Sync_Report_Report_v5_Using_Secret.ps1
├── Microsoft_Graph_API_urls.ps1
└── README.md
├── Powershell_Commands
├── Intune_Powershell_Commands_Examples.ps1
└── README.md
├── Printing
└── README.md
├── README.md
├── Remediations
├── Disable-Builtin-Administrator-account-Detection.ps1
├── Enable-Builtin-Administrator-account-Detection.ps1
├── Get-NetworkInformation-Detection.ps1
├── Get-Windows-SKU-Enterprise_or_Education-Detection.ps1
├── Get-Windows-licensing-activation-status-Detection.ps1
├── List_WLAN_profiles_Detection.ps1
├── Set-TimeZone Win32App Remediation
│ ├── 01-Requirements-script-Set-TimeZone-to-FLEStandardTime-Win32-Remediation.ps1
│ ├── 02-Detection-script-Set-TimeZone-to-FLEStandardTime-Win32-Remediation.ps1
│ ├── Set-TimeZone-to-FLEStandardTime-Win32-Remediation-Install-script.intunewin
│ ├── Win32 App notes.txt
│ ├── logo.png
│ └── source
│ │ └── Set-TimeZone-to-FLEStandardTime-Win32-Remediation-Install-script.ps1
├── Set-TimeZone-to-FLEStandardTime-Detection.ps1
├── Set-TimeZone-to-GreenwichStandardTime-Detection.ps1
├── WindowsUpdateRegistryValuesCheck-Detection.ps1
├── WindowsUpdateRegistryValuesCheck-Remediate.ps1
└── readme.MD
├── Reports
├── Create-IntuneAppAssignmentsReport.ps1
├── Create_IntuneConfigurationAssignments_HTML_Report.ps1
├── Create_IntuneMobileAppAssignments_HTML_Report.ps1
├── README.md
└── pics
│ ├── 01-DeviceConfigurationsReport_Overview.png
│ ├── 02-DeviceConfigurationsReport_Platform_by_profilename.png
│ ├── 03-DeviceConfigurationsReport_Targeted_to_AzureADGroup.png
│ ├── IntuneApplicationAssignmentReport.png
│ ├── IntuneApplicationAssignmentReportSortByAssignmentGroup.png
│ └── README.md
└── Troubleshooting
├── README.md
├── Show-IntuneIMELogsInOutGridView.ps1
└── pics
├── 01-Show-IntuneIMELogsInOutGridView.png
├── 02-Show-IntuneIMELogsInOutGridView.png
└── README.md
/Applocker/Create_Applocker_Intune_policy.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .Synopsis
3 | This script reads Applocker exported XML rules file and creates Intune Applocker configuration profile.
4 |
5 | .DESCRIPTION
6 | This script reads Applocker exported XML rules file and creates Intune Applocker custom oma-uri configuration profile.
7 |
8 | Script makes XML syntax check first. Only Enabled or AuditOnly mode rules are created to Intune. NotConfigured rules are ignored.
9 |
10 | Double check Intune rules configurations before applying to devices. Always test first on test devices before applying policy to production devices.
11 |
12 | Created json files (Intune Applocker configuration profile) and Graph API response are saved in current directory.
13 |
14 |
15 | Script uses Microsoft Intune Powershell management module.
16 |
17 | You can install Intune Powershell module to your user account without Administrator permissions with command:
18 | Install-Module -Name Microsoft.Graph.Intune -Scope CurrentUser
19 |
20 |
21 | Author:
22 | Petri.Paavola@yodamiitti.fi
23 | Senior Modern Management Principal
24 | Microsoft MVP - Windows and Devices for IT
25 |
26 | 2023-04-24
27 |
28 | Version 1.1
29 |
30 | https://github.com/petripaavola/Intune/tree/master/Applocker
31 |
32 | .PARAMETER FilePath
33 | Applocker exported rules XML file path
34 |
35 | .PARAMETER WhatIf
36 | Does XML file syntax check and creates rules json file. Filename is like: Applocker Policy - Created 2023-04-24 102623 - WhatIf.json
37 |
38 | .EXAMPLE
39 | .\Create_Applocker_Intune_policy.ps1 .\AppLocker.xml
40 | .EXAMPLE
41 | .\Create_Applocker_Intune_policy.ps1 -Filepath .\AppLocker.xml
42 | .EXAMPLE
43 | .\Create_Applocker_Intune_policy.ps1 -Filepath .\AppLocker.xml -WhatIf
44 |
45 | .INPUTS
46 | .OUTPUTS
47 | Script creates 2 json files. Intune Applocker XML configuration json file and Microsoft Graph API response json file.
48 |
49 | With option -WhatIf script creates Intune Applocker XML configuration json file which ends with -WhatIf.json
50 | .NOTES
51 | .LINK
52 | https://github.com/petripaavola/Intune/tree/master/Applocker
53 | #>
54 |
55 | [CmdletBinding()]
56 | Param(
57 | [Parameter(Mandatory=$True,
58 | HelpMessage = 'Enter Applocker XML file fullpath',
59 | ValueFromPipeline=$true,
60 | ValueFromPipelineByPropertyName=$true)]
61 | [Alias("XMLFilePath")]
62 | [String]$FilePath,
63 | [Parameter(Mandatory=$False,
64 | ValueFromPipeline=$true,
65 | ValueFromPipelineByPropertyName=$true)]
66 | [Switch]$WhatIf
67 | )
68 |
69 | # Script version
70 | $Version='1.1'
71 |
72 |
73 | # region Functions
74 | Function Validate-XMLSyntax {
75 | Param(
76 | $XMLFilePath
77 | )
78 |
79 | Try {
80 | [xml]$xmlTest = Get-Content $XMLFilePath -Encoding UTF8 -Raw
81 | return $True
82 | } catch {
83 | Write-Host "Error:`n$($_.Exception.Message)" -ForegroundColor Red
84 | return $False
85 | }
86 | }
87 |
88 | # Create a helper function to format the XmlNode string indents
89 | #
90 | # This function was provided and automatically documented by ChatGPT-4
91 | # Good example of AI usage in coding
92 | function FormatXmlNodeString {
93 | Param(
94 | $node
95 | )
96 |
97 | $xmlString = $node.OuterXml
98 |
99 | # Add newline before every opening and closing tag (excluding self-closing tags)
100 | $formattedXmlString = $xmlString -replace '(?<=[^<>]+>)(?=<[^<>]+>)', "`r`n" -replace '(?<=<[^<>]+>)(?=<[^<>]+>)', "`r`n"
101 |
102 | # Initialize indent level
103 | $indent = 0
104 |
105 | # Process XML string line by line to add proper indentation
106 | $formattedXmlString = ($formattedXmlString -split "`r`n" | ForEach-Object {
107 | # If the line starts with a closing tag, decrease the indent level by 2
108 | if ($_ -match '^') {
109 | $indent -= 2
110 | }
111 |
112 | # Create a new line with the current indent level
113 | $line = (' ' * $indent) + $_
114 |
115 | # If the line starts with an opening tag (not a closing or self-closing tag), increase the indent level by 2
116 | if ($_ -match '^<[^(?:\!|/)].*[^/]>$') {
117 | $indent += 2
118 | }
119 |
120 | # Return the formatted line
121 | $line
122 | }) -join "`r`n"
123 |
124 | return $formattedXmlString
125 | }
126 |
127 | # endregion Functions
128 |
129 | # region main
130 |
131 | Write-Host "Import Applocker XML rules and create Applocker policy to Intune. Version $version`n" -ForegroundColor Cyan
132 |
133 | if($WhatIf) {
134 | Write-Host "-WhatIf specified so not making any changes to Intune`n" -ForegroundColor Yellow
135 | }
136 |
137 | Write-Host "Processing file: $FilePath`n"
138 |
139 | # Check that XML file exists
140 | if(-not (Test-Path $FilePath)) {
141 | Write-Host "Could not find XML file: $FilePath" -ForegroundColor Red
142 | Write-Host "Script will exit`n" -ForegroundColor Yellow
143 | Exit 1
144 | }
145 |
146 | # Check XML file syntax
147 | Write-Host "Validate Applocker XML file syntax"
148 | if(Validate-XMLSyntax $FilePath) {
149 | Write-Host "OK`n" -ForegroundColor Green
150 | } Else {
151 | Write-Host "FAILED: XML syntax is NOT valid`n" -ForegroundColor Red
152 | Write-Host "Script will exit`n" -ForegroundColor Yellow
153 | Exit 1
154 | }
155 |
156 | # Read XML file string to variable casted as XML type
157 | [xml]$ApplockerXML = Get-Content $FilePath -Encoding UTF8 -Raw
158 |
159 | Write-Host "Import Applocker configurations from XML file (EXE/MSI/Appx/Script/DLL)"
160 |
161 | # Extract RuleCollections
162 | $exeRuleCollection = $ApplockerXML.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq 'Exe' -and (($_.EnforcementMode -eq 'Enabled') -or ($_.EnforcementMode -eq 'AuditOnly'))}
163 | $msiRuleCollection = $ApplockerXML.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq 'Msi' -and (($_.EnforcementMode -eq 'Enabled') -or ($_.EnforcementMode -eq 'AuditOnly')) }
164 | $scriptRuleCollection = $ApplockerXML.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq 'Script' -and (($_.EnforcementMode -eq 'Enabled') -or ($_.EnforcementMode -eq 'AuditOnly'))}
165 | $dllRuleCollection = $ApplockerXML.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq 'Dll' -and (($_.EnforcementMode -eq 'Enabled') -or ($_.EnforcementMode -eq 'AuditOnly'))}
166 | $appxRuleCollection = $ApplockerXML.AppLockerPolicy.RuleCollection | Where-Object { $_.Type -eq 'Appx' -and (($_.EnforcementMode -eq 'Enabled') -or ($_.EnforcementMode -eq 'AuditOnly'))}
167 |
168 |
169 | # Format RuleCollections XML strings
170 | # Add Newlines and indents for human readability
171 | if($exeRuleCollection) {
172 | $exeRuleCollectionXmlString = FormatXmlNodeString $exeRuleCollection
173 | } else {
174 | $exeRuleCollectionXmlString = $null
175 | }
176 |
177 | if($msiRuleCollection) {
178 | $msiRuleCollectionXmlString = FormatXmlNodeString $msiRuleCollection
179 | } else {
180 | $msiRuleCollectionXmlString = $null
181 | }
182 |
183 | if($scriptRuleCollection) {
184 | $scriptRuleCollectionXmlString = FormatXmlNodeString $scriptRuleCollection
185 | } else {
186 | $scriptRuleCollectionXmlString = $null
187 | }
188 |
189 | if($dllRuleCollection) {
190 | $dllRuleCollectionXmlString = FormatXmlNodeString $dllRuleCollection
191 | } else {
192 | $dllRuleCollectionXmlString = $null
193 | }
194 |
195 | if($appxRuleCollection) {
196 | $appxRuleCollectionXmlString = FormatXmlNodeString $appxRuleCollection
197 | } else {
198 | $appxRuleCollectionXmlString = $null
199 | }
200 |
201 | Write-Host "OK`n" -ForegroundColor Green
202 |
203 | <#
204 | # DEBUG
205 | # Output XML strings
206 | Write-Output "Exe RuleCollection XML:`n$exeRuleCollectionXmlString`n"
207 | Write-Output "Msi RuleCollection XML:`n$msiRuleCollectionXmlString`n"
208 | Write-Output "Script RuleCollection XML:`n$scriptRuleCollectionXmlString`n"
209 | Write-Output "Dll RuleCollection XML:`n$dllRuleCollectionXmlString`n"
210 | Write-Output "Appx RuleCollection XML:`n$appxRuleCollectionXmlString`n"
211 | Write-Host ""
212 | #>
213 |
214 | if(-not $WhatIf) {
215 |
216 | # Connect to Intune
217 | $IntunePowershellModule = Import-Module Microsoft.Graph.Intune -PassThru -ErrorAction SilentlyContinue
218 | if (-not $IntunePowershellModule) {
219 | Write-Host "Intune Powershell module not found!`n" -ForegroundColor Red
220 | Write-Host "You can install Intune Powershell module to your user account with command:"
221 | Write-Host "Install-Module -Name Microsoft.Graph.Intune -Scope CurrentUser" -ForegroundColor Cyan
222 | Write-Host "`nor you can install machine-wide Intune module with command:`nInstall-Module -Name Microsoft.Graph.Intune`n"
223 | Exit 1
224 | }
225 |
226 |
227 | Write-Host "Connecting to Intune using Powershell Intune-Module"
228 | Update-MSGraphEnvironment -SchemaVersion 'beta'
229 | $Success = $?
230 |
231 | if (-not $Success) {
232 | Write-Host "Failed to update MSGraph Environment schema to Beta!`n" -ForegroundColor Red
233 | Write-Host "Make sure you have installed Intune Powershell module"
234 |
235 | Write-Host "You can install Intune Powershell module to your user account with command:"
236 | Write-Host "Install-Module -Name Microsoft.Graph.Intune -Scope CurrentUser" -ForegroundColor Cyan
237 | Write-Host "`nor you can install machine-wide Intune module with command:`nInstall-Module -Name Microsoft.Graph.Intune`n"
238 | Exit 1
239 | }
240 |
241 | $MSGraphEnvironment = Connect-MSGraph
242 | $Success = $?
243 |
244 | if ($Success -and $MSGraphEnvironment) {
245 | $TenantId = $MSGraphEnvironment.tenantId
246 | $AdminUserUPN = $MSGraphEnvironment.upn
247 |
248 | Write-Host "Connected to Microsoft Intune / Microsoft Graph as user:`n$AdminUserUPN`n"
249 |
250 | } else {
251 | Write-Host "Could not connect to MSGraph!" -ForegroundColor Red
252 | Exit 1
253 | }
254 | } else {
255 | Write-Host "Skipping connecting to Intune Graph API management because -WhatIf is specified`n" -ForegroundColor Yellow
256 | }
257 |
258 | ##########################################
259 | # Create Applocker policy to Intune
260 |
261 | $DateTime = Get-Date -Format "yyyy-MM-dd HHmmss"
262 |
263 | $DisplayName = "Applocker Policy - Created $DateTime by $AdminUserUPN"
264 | $Description = "Creation date: $DateTime`nCreated by: $AdminUserUPN`nApplocker rules uploaded with Powershell script v$Version`n`nApplocker rules configured:`n"
265 | $Grouping = Get-Date -Format "yyyyMMddHHmmss"
266 |
267 | # Applocker custom oma-uri policy JSON template
268 | # We will add all rules to this template's omaSettings property if XML found
269 | $BodyTemplate = @"
270 | {
271 | "displayName": "$DisplayName",
272 | "description": "$Description",
273 | "roleScopeTagIds": [
274 | "0"
275 | ],
276 | "@odata.type": "#microsoft.graph.windows10CustomConfiguration",
277 | "omaSettings": [
278 | ]
279 | }
280 | "@
281 |
282 | $Body = $BodyTemplate | ConvertFrom-Json
283 |
284 | # Add rules XML information to Intune Policy creation request body
285 | if($exeRuleCollectionXmlString) {
286 | $ExeEnforcementMode = $exeRuleCollection.EnforcementMode
287 | Write-Host "Found Applocker EXE rules with EnforcementMode $ExeEnforcementMode"
288 | Write-Host "Add EXE rules to Intune policy creation request body"
289 |
290 | $ExeJSON = @"
291 | {
292 | "displayName": "Applocker EXE Rule with EnforcementMode $ExeEnforcementMode",
293 | "description": "",
294 | "omaUri": "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/EXE$Grouping/EXE/Policy",
295 | "@odata.type": "#microsoft.graph.omaSettingString",
296 | "value": ""
297 | }
298 | "@
299 |
300 | # Create Powershell object from JSON
301 | $ExeRule = $ExeJSON | ConvertFrom-Json
302 |
303 | # Add rule XML value to object
304 | $ExeRule.value = $exeRuleCollectionXmlString
305 |
306 | # Add EXE Rule to Body object
307 | $Body.omaSettings += $ExeRule
308 | $Success = $?
309 |
310 | if($Success) {
311 | Write-Host "OK`n" -ForegroundColor Green
312 |
313 | # Add rule type and enforcement information to Description"
314 | $Body.Description += "`tEXE with EnforcementMode $ExeEnforcementMode`n"
315 | } else {
316 | Write-Host "Failed" -ForegroundColor Red
317 | Write-Host "Script will exit`n" -ForegroundColor Yellow
318 | Exit 1
319 | }
320 |
321 | } else {
322 | Write-Host "Did NOT find Applocker EXE rules with EnforcementMode Enabled or AuditOnly`n" -ForegroundColor Yellow
323 | }
324 |
325 |
326 | if($msiRuleCollectionXmlString) {
327 | $MsiEnforcementMode = $msiRuleCollection.EnforcementMode
328 | Write-Host "Found Applocker MSI rules with EnforcementMode $MsiEnforcementMode"
329 | Write-Host "Add MSI rules to Intune policy creation request body"
330 |
331 | $MsiJSON = @"
332 | {
333 | "displayName": "Applocker MSI Rule with EnforcementMode $MsiEnforcementMode",
334 | "description": "",
335 | "omaUri": "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/MSI$Grouping/MSI/Policy",
336 | "@odata.type": "#microsoft.graph.omaSettingString",
337 | "value": ""
338 | }
339 | "@
340 |
341 | # Create Powershell object from JSON
342 | $MsiRule = $MsiJSON | ConvertFrom-Json
343 |
344 | # Add rule XML value to object
345 | $MsiRule.value = $msiRuleCollectionXmlString
346 |
347 | # Add MSI Rule to Body object
348 | $Body.omaSettings += $MsiRule
349 | $Success = $?
350 |
351 | if($Success) {
352 | Write-Host "OK`n" -ForegroundColor Green
353 |
354 | # Add rule type and enforcement information to Description"
355 | $Body.Description += "`tMSI with EnforcementMode $MsiEnforcementMode`n"
356 | } else {
357 | Write-Host "Failed" -ForegroundColor Red
358 | Write-Host "Script will exit`n" -ForegroundColor Yellow
359 | Exit 1
360 | }
361 |
362 | } else {
363 | Write-Host "Did NOT find Applocker MSI rules with EnforcementMode Enabled or AuditOnly`n" -ForegroundColor Yellow
364 | }
365 |
366 |
367 | if($scriptRuleCollectionXmlString) {
368 | $scriptEnforcementMode = $scriptRuleCollection.EnforcementMode
369 | Write-Host "Found Applocker Script rules with EnforcementMode $scriptEnforcementMode"
370 | Write-Host "Add Script rules to Intune policy creation request body"
371 |
372 | $ScriptJSON = @"
373 | {
374 | "displayName": "Applocker Script Rule with EnforcementMode $scriptEnforcementMode",
375 | "description": "",
376 | "omaUri": "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/Script$Grouping/Script/Policy",
377 | "@odata.type": "#microsoft.graph.omaSettingString",
378 | "value": ""
379 | }
380 | "@
381 |
382 | # Create Powershell object from JSON
383 | $ScriptRule = $ScriptJSON | ConvertFrom-Json
384 |
385 | # Add rule XML value to object
386 | $ScriptRule.value = $scriptRuleCollectionXmlString
387 |
388 | # Add MSI Rule to Body object
389 | $Body.omaSettings += $ScriptRule
390 | $Success = $?
391 |
392 | if($Success) {
393 | Write-Host "OK`n" -ForegroundColor Green
394 |
395 | # Add rule type and enforcement information to Description"
396 | $Body.Description += "`tScript with EnforcementMode $ScriptEnforcementMode`n"
397 | } else {
398 | Write-Host "Failed" -ForegroundColor Red
399 | Write-Host "Script will exit`n" -ForegroundColor Yellow
400 | Exit 1
401 | }
402 |
403 | } else {
404 | Write-Host "Did NOT find Applocker Script rules with EnforcementMode Enabled or AuditOnly`n" -ForegroundColor Yellow
405 | }
406 |
407 |
408 | if($dllRuleCollectionXmlString) {
409 | $dllEnforcementMode = $dllRuleCollection.EnforcementMode
410 | Write-Host "Found Applocker DLL rules with EnforcementMode $dllEnforcementMode"
411 | Write-Host "Add DLL rules to Intune policy creation request body"
412 |
413 | $DLLJSON = @"
414 | {
415 | "displayName": "Applocker DLL Rule with EnforcementMode $dllEnforcementMode",
416 | "description": "",
417 | "omaUri": "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/DLL$Grouping/DLL/Policy",
418 | "@odata.type": "#microsoft.graph.omaSettingString",
419 | "value": ""
420 | }
421 | "@
422 |
423 | # Create Powershell object from JSON
424 | $DLLRule = $DLLJSON | ConvertFrom-Json
425 |
426 | # Add rule XML value to object
427 | $DLLRule.value = $dllRuleCollectionXmlString
428 |
429 | # Add MSI Rule to Body object
430 | $Body.omaSettings += $DLLRule
431 | $Success = $?
432 |
433 | if($Success) {
434 | Write-Host "OK`n" -ForegroundColor Green
435 |
436 | # Add rule type and enforcement information to Description"
437 | $Body.Description += "`tDLL with EnforcementMode $dllEnforcementMode`n"
438 | } else {
439 | Write-Host "Failed" -ForegroundColor Red
440 | Write-Host "Script will exit`n" -ForegroundColor Yellow
441 | Exit 1
442 | }
443 |
444 | } else {
445 | Write-Host "Did NOT find Applocker DLL rules with EnforcementMode Enabled or AuditOnly`n" -ForegroundColor Yellow
446 | }
447 |
448 |
449 | if($appxRuleCollectionXmlString) {
450 | $appxEnforcementMode = $appxRuleCollection.EnforcementMode
451 | Write-Host "Found Applocker Appx rules with EnforcementMode $appxEnforcementMode"
452 | Write-Host "Add Appx rules to Intune policy creation request body"
453 |
454 | $AppxJSON = @"
455 | {
456 | "displayName": "Applocker Appx Rule with EnforcementMode $appxEnforcementMode",
457 | "description": "",
458 | "omaUri": "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/Appx$Grouping/StoreApps/Policy",
459 | "@odata.type": "#microsoft.graph.omaSettingString",
460 | "value": ""
461 | }
462 | "@
463 |
464 | # Create Powershell object from JSON
465 | $AppxRule = $AppxJSON | ConvertFrom-Json
466 |
467 | # Add rule XML value to object
468 | $AppxRule.value = $appxRuleCollectionXmlString
469 |
470 | # Add MSI Rule to Body object
471 | $Body.omaSettings += $AppxRule
472 | $Success = $?
473 |
474 | if($Success) {
475 | Write-Host "OK`n" -ForegroundColor Green
476 |
477 | # Add rule type and enforcement information to Description"
478 | $Body.Description += "`tAppx with EnforcementMode $appxEnforcementMode`n"
479 |
480 | } else {
481 | Write-Host "Failed" -ForegroundColor Red
482 | Write-Host "Script will exit`n" -ForegroundColor Yellow
483 | Exit 1
484 | }
485 |
486 | } else {
487 | Write-Host "Did NOT find Applocker Appx rules with EnforcementMode Enabled or AuditOnly`n" -ForegroundColor Yellow
488 | }
489 |
490 |
491 | $BodyJson = $Body | ConvertTo-Json -Depth 5
492 |
493 | # Save JSON to file
494 | if($WhatIf) {
495 | $ExportFilePath = "$PSScriptRoot\Applocker Policy - Created $DateTime - WhatIf.json"
496 | } else {
497 | $ExportFilePath = "$PSScriptRoot\Applocker Policy - Created $DateTime.json"
498 | }
499 | Write-Host "Export Applocker Intune policy JSON to file: $ExportFilePath"
500 | $BodyJson | Out-File -FilePath $ExportFilePath -Encoding UTF8
501 | $Success = $?
502 | if($Success) {
503 | Write-Host "OK`n" -ForegroundColor Green
504 | } else {
505 | Write-Host "Failed`n" -ForegroundColor Red
506 | }
507 |
508 | if($WhatIf) {
509 | Write-Host "What if: Would upload Applocker configuration to Intune with POST body json:`n`n$BodyJson`n"
510 |
511 | } else {
512 | Write-Host "Create Applocker Configuration profile to Intune"
513 |
514 | $Url = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations"
515 | $MSGraphRequest = Invoke-MSGraphRequest -Url $url -Content $BodyJson.ToString() -HttpMethod 'POST'
516 | $Success = $?
517 | if($Success -and $MSGraphRequest) {
518 | Write-Host "OK`n" -ForegroundColor Green
519 | Write-Host "Graph API response:`n"
520 | $MSGraphRequest
521 | Write-Host ""
522 |
523 | $ResponseFilePath = "$PSScriptRoot\Applocker Policy - Created $DateTime - GraphAPI response.json"
524 | Write-Host "Save Graph API response to file just in case that is needed later:"
525 | Write-Host "$ResponseFilePath"
526 |
527 | $MSGraphRequest | ConvertTo-Json -Depth 5 | Out-File -FilePath $ResponseFilePath -Encoding UTF8
528 | $Success = $?
529 | if($Success) {
530 | Write-Host "OK`n" -ForegroundColor Green
531 | Write-Host "Check Applocker Configuration profile in Intune:`n$DisplayName`n"
532 | } else {
533 | Write-Host "Failed`n" -ForegroundColor Red
534 | }
535 |
536 | } else {
537 | Write-Host "There was possible problems uploading Applocker policy to Intune!" -ForegroundColor Yellow
538 | $MSGraphRequest
539 | }
540 | }
541 |
542 | Write-Host "All done."
543 |
544 |
545 | # endregion main
--------------------------------------------------------------------------------
/Applocker/README.md:
--------------------------------------------------------------------------------
1 | # Create Intune Applocker configuration profile from Applocker XML file #
2 |
3 | Create Intune Applocker Configuration Profile with this script.
4 |
5 | ## Quick links to files:
6 | * [Create_Applocker_Intune_policy.ps1](https://github.com/petripaavola/Intune/blob/master/Applocker/Create_Applocker_Intune_policy.ps1)
7 |
8 | ## Description
9 |
10 | This script reads Applocker exported XML rules file and creates Intune Applocker custom oma-uri configuration profile.
11 |
12 | Script makes XML syntax check first. Only **Enabled** or **AuditOnly** mode rules are created to Intune. NotConfigured rules are ignored.
13 |
14 | **Double check Intune rules configurations before applying to devices. Always test first on test devices before applying policy to production devices.**
15 |
16 | Script saves 2 JSON files locally to script folder:
17 | * **Intune Applocker configuration profile**
18 | * For example: *Applocker Policy - Created 2023-04-24 103049.json*
19 | * **Graph API response are saved in current directory**
20 | * For example. *Applocker Policy - Created 2023-04-24 103712 - GraphAPI response.json*
21 |
22 | With option **-WhatIf** script does XML syntax check and creates Intune configuration JSON file locally.
23 | Filename example is: *Applocker Policy - Created 2023-04-24 103044 - WhatIf.json*
24 |
25 | ### Intune Applocker configuration policy ###
26 | Script creates Intune Applocker policies with naming syntax:
27 | **Applocker Policy - Created 2023-04-24 103712 by admin.username@organization.com**
28 |
29 | **Test and double check Applocker configuration profile XML syntax before production deployment.**
30 |
31 | ### Parameters ###
32 |
33 | **-FilePath**
34 | Path to Applocker XML file
35 | ```
36 | .\Create_Applocker_Intune_policy.ps1 -Filepath .\AppLocker.xml
37 | ```
38 |
39 | **-WhatIf**
40 | Does XML file syntax check and creates rules json file. Filename is like: Applocker Policy - Created 2023-04-24 102623 - WhatIf.json
41 | ```
42 | .\Create_Applocker_Intune_policy.ps1 -Filepath .\AppLocker.xml -WhatIf
43 | ```
44 |
45 | ### Prerequisities ###
46 |
47 | Script uses Intune Powershell management module **Microsoft.Graph.Intune**.
48 |
49 | You can install Intune Powershell management module to your user account with command
50 | ```
51 | Install-Module -Name Microsoft.Graph.Intune -Scope CurrentUser
52 | ```
53 |
--------------------------------------------------------------------------------
/Apps/Audacity_3.5.1_Intune_Win32_Custom_DetectionCheck_Script.ps1:
--------------------------------------------------------------------------------
1 | # Intune Win32 App Audacity Custom Detection check script for version 3.5.1
2 | #
3 | # This returns compliant with equal to 3.5.1 version
4 | #
5 | #
6 | # Petri.Paavola@yodamiitti.fi
7 | # Windows MVP - Windows and Intune
8 | # 2024-11-01
9 | #
10 | # Original script source:
11 | # https://github.com/petripaavola/Intune/blob/master/Apps/Audacity_3.5.1_Intune_Win32_Custom_DetectionCheck_Script.ps1
12 |
13 |
14 |
15 | # Check if file exists
16 | if(-not (Test-Path "C:\Program Files\Audacity\audacity.exe")) {
17 | # File does not exist
18 | exit 1
19 | }
20 |
21 |
22 | $FileVersionString = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Program Files\Audacity\audacity.exe").FileVersion
23 |
24 | # Convert , -> .
25 | $FileVersionString = $FileVersionString.Replace(',','.')
26 |
27 | #The below line trims the spaces before and after the version name
28 | $FileVersionString = $FileVersionString.Trim();
29 |
30 | # Cast variable to [version]
31 | $FileVersion = [version]$FileVersionString
32 |
33 |
34 | # We could also get version with this command in most other cases
35 | #$file = Get-ChildItem "C:\Program Files\Audacity\audacity.exe"
36 | #[version]$FileVersion = $file.versioninfo.fileversion
37 |
38 |
39 | if ($FileVersion -eq "3.5.1.0" ) {
40 | # App detected
41 |
42 | # Write the version to STDOUT by default
43 | # Write anything to StdOut and exit 0 to make application show as detected
44 | Write-Host "$FileVersion"
45 | exit 0
46 | }
47 | else {
48 | # App NOT detected
49 |
50 | #Exit with non-zero failure code
51 | exit 1
52 | }
53 |
--------------------------------------------------------------------------------
/Apps/Create_Multiple_IntuneMobileAppAssignments_Out-GridView.ps1:
--------------------------------------------------------------------------------
1 | # Create Multiple Intune Application assignments for selected Apps
2 | # Uses Out-GridView to show GUI to select App(s), AzureADGroup and Intent
3 | #
4 | # Petri.Paavola@yodamiitti.fi
5 | # Microsoft MVP - Windows and Devices for IT
6 | #
7 | # 27.4.2021
8 |
9 |
10 | function Create-IntuneApplicationAssignment {
11 | Param(
12 | $AzureADGroupId,
13 | $mobileAppId,
14 | $AssignmentIntent
15 | )
16 |
17 | # Choices are: 'available', 'required', 'uninstall', 'availableWithoutEnrollment'
18 |
19 | $TargetObject = $null
20 | $TargetObject = New-Object PSObject
21 | $TargetObject | Add-Member NoteProperty '@odata.type' '#microsoft.graph.groupAssignmentTarget'
22 | $TargetObject | Add-Member NoteProperty 'groupId' $AzureADGroupId
23 |
24 | # Fix json - custom-object problem
25 | # This is not needed anymore 20190220
26 | #$TargetObject = $TargetObject |ConvertTo-json |ConvertFrom-json
27 |
28 | # Create Intune Application assignment
29 | New-DeviceAppManagement_MobileApps_Assignments -mobileAppId $mobileAppId -intent $AssignmentIntent -target $TargetObject
30 |
31 | $Success = $?
32 | return $Success
33 | }
34 |
35 | #####
36 |
37 | # Update Graph API schema to beta to get Win32LobApps and possible other new features also
38 | Update-MSGraphEnvironment -SchemaVersion 'beta'
39 |
40 | $MSGraphEnvironment = Connect-MSGraph
41 | $Success = $?
42 |
43 | if (-not $Success) {
44 | Write-Error "Error connecting to Intune!"
45 | Exit 1
46 | }
47 |
48 | # Get AzureAD groups and show them in Out-GridView. User can select only 1 group
49 |
50 | # Notice we probably get only part of groups because GraphAPI returns limited number of groups
51 | $groups = Get-AADGroup -Filter 'securityEnabled eq true'
52 |
53 | # Quick workaround to get all Security Groups and also get actual objects and not .Value -attribute
54 | $AllGroups = Get-MSGraphAllPages -SearchResult $groups
55 |
56 | if(-not $AllGroups) {
57 | Write-Error "Could not find any AzureAD Groups!"
58 | Exit 1
59 | }
60 |
61 | # Show AzureAD groups in Out-GridView with selected properties and save selected AzureADGroup to variable
62 | Write-Output "Select AzureAD Group from Out-GridView"
63 | $SelectedAzureADGroup = $AllGroups | Select displayName, description, createdDateTime, id, groupTypes | Sort displayName | Out-GridView -Title "Select AzureAD Group to make assignment" -OutputMode Single
64 |
65 | # Exit if nothing was selected in Out-GridView
66 | if (-not $SelectedAzureADGroup) { Write-Output "No groups selected, exiting..."; Exit 0 }
67 |
68 | #####
69 |
70 | # Get App information and show Apps in Out-GridView
71 |
72 | # We need assignments info to check existing assignments so -Expand assignment option is needed here
73 | $Apps = Get-DeviceAppManagement_MobileApps -Expand assignments
74 |
75 | # Quick workaround to get all Apps and also get actual objects and not .Value -attribute
76 | $AllApps = Get-MSGraphAllPages -SearchResult $Apps
77 |
78 | if(-not $AllApps) {
79 | Write-Error "Could not find any Intune Apps!"
80 | Exit 1
81 | }
82 |
83 | # Show Apps in Out-GridView, show only specified app properties
84 | Write-Output "Select Apps in Out-GridView to App assignments creation for group: $($SelectedApp.assignmentTargetGroupDisplayName)"
85 | $SelectedApps = $AllApps | Select '@odata.type', vppTokenAppleId, displayName, productVersion, publisher, fileName, size, commandLine, productCode, publishingState, createdDateTime, lastModifiedDateTime, id| Sort displayName | Out-GridView -PassThru -Title 'Select Application(s) to make assignment'
86 |
87 | # Exit if nothing was selected in Out-GridView
88 | if (-not $SelectedApps) { Write-Output "No Apps selected, exiting..."; Exit 0 }
89 |
90 | #####
91 |
92 | # Get Intent with Out-GridView
93 | Write-Output "Select Intent for App Assignment"
94 | $SelectedIntent = @('required', 'available', 'uninstall', 'availableWithoutEnrollment') | Out-GridView -Title 'Select Intent for application assignment' -OutputMode Single
95 |
96 | # Exit if nothing was selected in Out-GridView
97 | if (-not $SelectedIntent) { Write-Output "No App assignment intent selected, exiting..."; Exit 0 }
98 |
99 |
100 | # Make assignments for all selected Apps
101 | foreach ($SelectedApp in $SelectedApps) {
102 |
103 | # Filter out those Apps that already have assignment for selected AzureADGroup
104 | $ExistingAssignment = $AllApps | Where-Object { ($_.id -eq $SelectedApp.id) -and ($_.Assignments.target.groupid -eq $SelectedAzureADGroup.id) }
105 |
106 | # If there is already is existing Assignment then show warning and continue to next App in foreach loop
107 | if ($ExistingAssignment) {
108 | # There is already assignment for this AzureADGroup
109 | # Show warning and skip this App assignment
110 |
111 | $intent = $ExistingAssignment | Where-Object { $_.Assignments.target.groupid -eq $SelectedAzureADGroup.id } | Select -ExpandProperty Assignments | Select -ExpandProperty Intent
112 | `Write-Host "Warning: Existing $intent Assignment for App: `"$($SelectedApp.displayName)`"" found for AzureADGroup: `"$($SelectedAzureADGroup.displayName)`"". Skipping this App assignment..." -ForegroundColor Yellow
113 |
114 | # Skip this App in foreach loop and process to next App
115 | Continue
116 | }
117 |
118 | # Create Application assignment - most important line in this script
119 | $Success = Create-IntuneApplicationAssignment $SelectedAzureADGroup.Id $SelectedApp.Id $SelectedIntent
120 | if ($Success) {
121 | Write-Host "Success: Created $SelectedIntent Application assignment for Application: `"$($SelectedApp.DisplayName)`" for AzureADGroup: `"$($SelectedAzureADGroup.DisplayName)`"" -Foreground Green
122 | }
123 | else {
124 | Write-Host "Failed: Error creating $SelectedIntent Application assignment for Application: `"$($SelectedApp.DisplayName)`" for AzureADGroup: `"$($SelectedAzureADGroup.DisplayName)`"" -Foreground Red
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Apps/Delete_Multiple_IntuneMobileAppAssignments_Out-GridView.ps1:
--------------------------------------------------------------------------------
1 | # Delete Multiple Intune Application assignments for selected Apps
2 | # Uses Out-GridView to show GUI to select AzureADGroup and App(s),
3 | #
4 | # Petri.Paavola@yodamiitti.fi
5 | # Microsoft MVP - Windows and Devices for IT
6 | #
7 | # 27.4.2021
8 |
9 |
10 | ######
11 |
12 | # Update Graph API schema to beta to get Win32LobApps and possible other new features also
13 | Update-MSGraphEnvironment -SchemaVersion 'beta'
14 |
15 | $MSGraphEnvironment = Connect-MSGraph
16 | $Success = $?
17 |
18 | if (-not $Success) {
19 | Write-Error "Error connecting to Intune!"
20 | Exit 1
21 | }
22 |
23 | # Get AzureAD groups and show them in Out-GridView. User can select only 1 group
24 |
25 | # Notice we probably get only part of groups because GraphAPI returns limited number of groups
26 | $groups = Get-AADGroup -Filter 'securityEnabled eq true'
27 |
28 | # Quick workaround to get all Security Groups and also get actual objects and not .Value -attribute
29 | $AllGroups = Get-MSGraphAllPages -SearchResult $groups
30 |
31 | if(-not $AllGroups) {
32 | Write-Error "Could not find any AzureAD Groups!"
33 | Exit 1
34 | }
35 |
36 | # Show AzureAD groups in Out-GridView with selected properties and save selected AzureADGroup to variable
37 | Write-Output "Select AzureAD Group from Out-GridView"
38 | $SelectedAzureADGroup = $AllGroups | Select displayName, description, createdDateTime, id, groupTypes | Sort displayName | Out-GridView -Title "Select AzureAD Group to delete from App assignment" -OutputMode Single
39 |
40 | # Exit if nothing was selected in Out-GridView
41 | if (-not $SelectedAzureADGroup) { Write-Output "No groups selected, exiting..."; Exit 0 }
42 |
43 | ######
44 |
45 | # Get App information and show Apps in Out-GridView
46 |
47 | # We need assignments info so -Expand assignment option is needed here
48 | $Apps = Get-DeviceAppManagement_MobileApps -Expand assignments
49 |
50 | # Quick workaround to get all Apps and also get actual objects and not .Value -attribute
51 | $AllApps = Get-MSGraphAllPages -SearchResult $Apps
52 |
53 | if(-not $AllApps) {
54 | Write-Error "Could not find any Intune Apps!"
55 | Exit 1
56 | }
57 |
58 | # Find apps which have assignments to our selected AzureAD Group id
59 | # Check data syntax from GraphAPI with request: https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$expand=assignments
60 | # or convert $Apps to json to get more human readable format: $Apps | ConvertTo-JSON
61 | $AppsWithAssignmentToSelectedGroup = $AllApps | Where-Object { $_.assignments.target.groupid -eq $SelectedAzureADGroup.id }
62 |
63 | # Exit if there were no assignments to selected AzureAD Group
64 | if (-not $AppsWithAssignmentToSelectedGroup) {
65 | Write-Output "No Application assignments found for AzureAD Group: $($SelectedAzureADGroup.displayName)"
66 | Exit 0
67 | }
68 |
69 | # Create custom object array and gather necessary app and assignment information. We need appId and assignmentId to remove selected assignments. All other attributes are for humans
70 | $AppsWithAssignmentInformation = @()
71 |
72 | # Go through each app and save necessary information to custom object
73 | Foreach ($App in $AppsWithAssignmentToSelectedGroup) {
74 | $Assignment = $App.Assignments | Where-Object { $_.target.groupid -eq $SelectedAzureADGroup.id }
75 |
76 | if($App.vppTokenAppleId) { $vppTokenAppleId = $App.vppTokenAppleId} else { $vppTokenAppleId = $null }
77 |
78 | $properties = @{
79 | '@odata.type' = $App.'@odata.type'
80 | vppTokenAppleId = $vppTokenAppleId
81 | displayname = $App.displayname
82 | productVersion = $App.productVersion
83 | publisher = $App.publisher
84 | filename = $App.filename
85 | createdDateTime = $App.createdDateTime
86 | lastModifiedDateTime = $App.lastModifiedDateTime
87 | id = $App.id
88 | assignmentId = $Assignment.id
89 | assignmentIntent = $Assignment.intent
90 | assignmentTargetGroupId = $Assignment.target.groupid
91 | assignmentTargetGroupDisplayName = $SelectedAzureADGroup.displayName
92 | }
93 |
94 | # Create new custom object every time inside foreach-loop
95 | # This is really important step to do inside foreach-loop!
96 | # If you create custom object outside of foreach then you would edit same custom object on every foreach cycle resulting only 1 app in custom object array
97 | $CustomObject = New-Object -TypeName PSObject -Prop $properties
98 |
99 | # Add custom object to our custom object array.
100 | $AppsWithAssignmentInformation += $CustomObject
101 | }
102 |
103 |
104 | # Show Apps in Out-GridView, user can select multiple applications where assignments are deleted
105 | Write-Output "Select Apps in Out-GridView to delete App assignments for group: $($SelectedApp.assignmentTargetGroupDisplayName)"
106 | $SelectedApps = $AppsWithAssignmentInformation | Select '@odata.type', vppTokenAppleId, displayName, assignmentIntent, assignmentTargetGroupDisplayName, publisher, productVersion, filename, createdDateTime, lastModifiedDateTime, id, assignmentId, assignmentTargetGroupId | Sort displayName | Out-GridView -PassThru -Title "Select Application(s) to delete assignment from group: $($SelectedAzureADGroup.displayName)"
107 |
108 | # Exit if nothing was selected in Out-GridView
109 | if (-not $SelectedApps) { Write-Output "No Apps selected, exiting..."; Exit 0 }
110 |
111 | # Delete App assignment for selected Apps
112 | foreach ($SelectedApp in $SelectedApps) {
113 |
114 | # Remove Application Assignment - most important line in this script
115 | Remove-DeviceAppManagement_MobileApps_Assignments -mobileAppAssignmentId $SelectedApp.assignmentId -mobileAppId $SelectedApp.id
116 | $Success = $?
117 |
118 | if ($Success) {
119 | Write-Host "SUCCESS: Deleting $($SelectedApp.assignmentIntent) assignment from application $($SelectedApp.displayname) for group: $($SelectedApp.assignmentTargetGroupDisplayName)" -Foreground Green
120 | }
121 | else {
122 | Write-Host "FAILED: Deleting $($SelectedApp.assignmentIntent) assignment from application $($SelectedApp.displayname) for group: $($SelectedApp.assignmentTargetGroupDisplayName)" -Foreground Red
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Apps/README.md:
--------------------------------------------------------------------------------
1 | # Scripts, tips and tricks to manage Intune Apps
2 |
--------------------------------------------------------------------------------
/Apps/Show_IntuneMobileAppAssignments_to_selected_AzureADGroup_in_Out-GridView.ps1:
--------------------------------------------------------------------------------
1 | # Show Intune Application assignments for selected AzureADGroup
2 | # Uses Out-GridView to show GUI to select AzureADGroup and App(s)
3 | #
4 | # Petri.Paavola@yodamiitti.fi
5 | # Microsoft MVP - Windows and Devices for IT
6 | #
7 | # 27.4.2021
8 |
9 |
10 | ######
11 |
12 | # Update Graph API schema to beta to get Win32LobApps and possible other new features also
13 | Update-MSGraphEnvironment -SchemaVersion 'beta'
14 |
15 | $MSGraphEnvironment = Connect-MSGraph
16 | $Success = $?
17 |
18 | if (-not $Success) {
19 | Write-Error "Error connecting to Intune!"
20 | Exit 1
21 | }
22 |
23 | # Get AzureAD groups and show them in Out-GridView. User can select only 1 group
24 |
25 | # Notice we probably get only part of groups because GraphAPI returns limited number of groups
26 | $groups = Get-AADGroup -Filter 'securityEnabled eq true'
27 |
28 | # Quick workaround to get all Security Groups and also get actual objects and not .Value -attribute
29 | $AllGroups = Get-MSGraphAllPages -SearchResult $groups
30 |
31 | if(-not $AllGroups) {
32 | Write-Error "Could not find any AzureAD Groups!"
33 | Exit 1
34 | }
35 |
36 | # Show AzureAD groups in Out-GridView with selected properties and save selected AzureADGroup to variable
37 | Write-Output "Select AzureAD Group from Out-GridView"
38 | $SelectedAzureADGroup = $AllGroups | Select displayName, description, createdDateTime, id, groupTypes | Sort displayName | Out-GridView -Title "Select AzureAD Group to show it's App Assignments" -OutputMode Single
39 |
40 | # Exit if nothing was selected in Out-GridView
41 | if (-not $SelectedAzureADGroup) { Write-Output "No groups selected, exiting..."; Exit 0 }
42 |
43 | ######
44 |
45 | # Get App information and show Apps in Out-GridView
46 |
47 | # We need assignments info so -Expand assignment option is needed here
48 | $Apps = Get-DeviceAppManagement_MobileApps -Expand assignments
49 |
50 | # Quick workaround to get all Apps and also get actual objects and not .Value -attribute
51 | $AllApps = Get-MSGraphAllPages -SearchResult $Apps
52 |
53 | if(-not $AllApps) {
54 | Write-Error "Could not find any Intune Apps!"
55 | Exit 1
56 | }
57 |
58 | # Find apps which have assignments to our selected AzureAD Group id
59 | # Check data syntax from GraphAPI with request: https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$expand=assignments
60 | # or convert $Apps to json to get more human readable format: $Apps | ConvertTo-JSON
61 | $AppsWithAssignmentToSelectedGroup = $AllApps | Where-Object { $_.assignments.target.groupid -eq $SelectedAzureADGroup.id }
62 |
63 | # Exit if there were no assignments to selected AzureAD Group
64 | if (-not $AppsWithAssignmentToSelectedGroup) {
65 | Write-Output "No Application assignments found for AzureAD Group: $($SelectedAzureADGroup.displayName)"
66 | Exit 0
67 | }
68 |
69 | # Create custom object array and gather necessary app and assignment information. We need appId and assignmentId to remove selected assignments. All other attributes are for humans
70 | $AppsWithAssignmentInformation = @()
71 |
72 | # Go through each app and save necessary information to custom object
73 | Foreach ($App in $AppsWithAssignmentToSelectedGroup) {
74 | $Assignment = $App.Assignments | Where-Object { $_.target.groupid -eq $SelectedAzureADGroup.id }
75 |
76 | if($App.vppTokenAppleId) { $vppTokenAppleId = $App.vppTokenAppleId} else { $vppTokenAppleId = $null }
77 |
78 | $properties = @{
79 | '@odata.type' = $App.'@odata.type'
80 | vppTokenAppleId = $vppTokenAppleId
81 | displayname = $App.displayname
82 | productVersion = $App.productVersion
83 | publisher = $App.publisher
84 | filename = $App.filename
85 | createdDateTime = $App.createdDateTime
86 | lastModifiedDateTime = $App.lastModifiedDateTime
87 | id = $App.id
88 | assignmentId = $Assignment.id
89 | assignmentIntent = $Assignment.intent
90 | assignmentTargetGroupId = $Assignment.target.groupid
91 | assignmentTargetGroupDisplayName = $SelectedAzureADGroup.displayName
92 | }
93 |
94 | # Create new custom object every time inside foreach-loop
95 | # This is really important step to do inside foreach-loop!
96 | # If you create custom object outside of foreach then you would edit same custom object on every foreach cycle resulting only 1 app in custom object array
97 | $CustomObject = New-Object -TypeName PSObject -Prop $properties
98 |
99 | # Add custom object to our custom object array.
100 | $AppsWithAssignmentInformation += $CustomObject
101 | }
102 |
103 |
104 | # Show Apps in Out-GridView
105 | $SelectedApps = $AppsWithAssignmentInformation | Select '@odata.type', vppTokenAppleId, displayName, assignmentIntent, assignmentTargetGroupDisplayName, publisher, productVersion, filename, createdDateTime, lastModifiedDateTime, id, assignmentId, assignmentTargetGroupId | Sort displayName | Out-GridView -Title "Found these App Assignment for AzureADGroup: $($SelectedAzureADGroup.displayName)"
106 |
--------------------------------------------------------------------------------
/Apps/install_Audacity_3.5.1-EXE-example.ps1:
--------------------------------------------------------------------------------
1 | # Install Audacity + MP3 Lame encoder
2 | # IntuneWin32 EXE Powershell script install example
3 | #
4 | #
5 | # Petri.Paavola@yodamiitti.fi
6 | # Windows MVP - Windows and Intune
7 | # 2024-11-01
8 | #
9 | # Original script source:
10 | # https://github.com/petripaavola/Intune/blob/master/Apps/install_Audacity_3.5.1-EXE-example.ps1
11 | #
12 | # Intune install command:
13 | # Powershell.exe -ExecutionPolicy Bypass -File install_Audacity_3.5.1-EXE-example.ps1
14 |
15 |
16 | #
17 | #
18 | # # #
19 | # #
20 | #
21 |
22 | # Configure these values
23 | $SoftwareName = 'Audacity'
24 | $version = "3.5.1"
25 |
26 | $InstallerFilePath = "$PSScriptRoot\audacity-win-3.5.1-64bit.exe"
27 | $Arguments = @(
28 | '/verysilent'
29 | '/norestart'
30 | )
31 |
32 | #
33 | # #
34 | # # #
35 | #
36 | #
37 |
38 |
39 |
40 | # Start script in 64bit environment if script was started in 32bit environment
41 | # Intune Win32 application install process starts in 32bit process by default (2024-11-01)
42 | # Few commands will require native bit command to work. Examples pnputil.exe and dism.exe
43 | # Below 64bit workaround is provided by Oliver Kieselbach
44 | # Original example:
45 | # https://github.com/okieselbach/Intune/blob/master/ManagementExtension-Samples/IntunePSTemplate.ps1
46 | if (-not [System.Environment]::Is64BitProcess)
47 | {
48 | # start new PowerShell as x64 bit process, wait for it and gather exit code and standard error output
49 | $sysNativePowerShell = "$($PSHOME.ToLower().Replace("syswow64", "sysnative"))\powershell.exe"
50 |
51 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo
52 | $pinfo.FileName = $sysNativePowerShell
53 | $pinfo.Arguments = "-ex bypass -file `"$PSCommandPath`""
54 | $pinfo.RedirectStandardError = $true
55 | $pinfo.RedirectStandardOutput = $true
56 | $pinfo.CreateNoWindow = $true
57 | $pinfo.UseShellExecute = $false
58 | $p = New-Object System.Diagnostics.Process
59 | $p.StartInfo = $pinfo
60 | $p.Start() | Out-Null
61 | $p.WaitForExit() # Wait for the 64-bit process to complete
62 |
63 | $exitCode = $p.ExitCode
64 |
65 | $stderr = $p.StandardError.ReadToEnd()
66 |
67 | if ($stderr) { Write-Error -Message $stderr }
68 |
69 | exit $exitCode
70 |
71 | } else {
72 |
73 | #############################################################
74 | # Main script starts here
75 |
76 | # Start logging
77 | Start-Transcript "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin.log" -Append
78 |
79 | # Running inside Try so we can catch fatal errors
80 | try {
81 |
82 | Write-Host "Install $SoftwareName $version"
83 |
84 |
85 | # Check that $InstallerFilePath exists
86 | if (-not (Test-Path $InstallerFilePath)) {
87 | Write-Host "Installer file not found: $InstallerFilePath"
88 | Write-Host "Exiting installation with error code 9999"
89 | Stop-Transcript
90 | Exit 9999
91 | }
92 |
93 |
94 | # Log file path for stdOut and stdError
95 | $RedirectStandardOutputFilePath = "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin_RedirectStandardOutput.log"
96 | $RedirectStandardErrorFilePath = "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin_RedirectStandardError.log"
97 |
98 |
99 | # We use Start-Process which gets most probability the ExitCode
100 | # Some really really rare cases $LastExitCode does not work, so this works better
101 | $Process = Start-Process $InstallerFilePath -ArgumentList $Arguments -Wait -Passthru -NoNewWindow -RedirectStandardOutput $RedirectStandardOutputFilePath -RedirectStandardError $RedirectStandardErrorFilePath
102 | $Process.WaitForExit()
103 | $ExitCode = $Process.ExitCode
104 |
105 | if($ExitCode -eq 0) {
106 | # Installation was successful
107 |
108 | Write-Host "$SoftwareName $version installation succeeded with ExitCode $ExitCode"
109 | } else {
110 | # Installation failed
111 |
112 | Write-Host "Error installing $SoftwareName $version. ExitCode: $ExitCode"
113 | Write-Host "Exiting installation with ExitCode $ExitCode"
114 | Stop-Transcript
115 | Exit $ExitCode
116 | }
117 |
118 | # Remove Desktop shortcut
119 | # This is best try so app installation will not fail if this for any reason did not succeed
120 | $DesktopShortcutPath = "C:\Users\Public\Desktop\Audacity.lnk"
121 | if(Test-Path $DesktopShortcutPath) {
122 | Write-Host "Delete desktop shortcut: $DesktopShortcutPath"
123 | Remove-Item $DesktopShortcutPath -Force -ErrorAction SilentlyContinue
124 | Write-Host "Success: $?"
125 | }
126 |
127 |
128 | Write-Host "Copy lame_enc.dll to Audacity installation folder"
129 | Copy-Item "$PSScriptRoot\lame_enc.dll" -Destination "C:\Program Files\Audacity" -Force
130 | $Success = $?
131 |
132 | if($Success) {
133 | Write-Host "Success copying file."
134 |
135 | Write-Host "$SoftwareName $version installation succeeded"
136 | Write-Host "Exiting installation with ExitCode 0"
137 | Stop-Transcript
138 | Exit 0
139 | } else {
140 | Write-Host "Error copying lame_enc.dll file"
141 | Write-Host "Exiting installation with ExitCode 2"
142 | Stop-Transcript
143 | Exit 2
144 | }
145 |
146 | } catch {
147 | # Handle any unexpected errors
148 |
149 | Write-Host "An unexpected fatal error occurred: $_"
150 | Write-Host "Exiting installation with ExitCode 99999"
151 | Stop-Transcript
152 | Exit 99999
153 |
154 | }
155 | }
--------------------------------------------------------------------------------
/Apps/install_LibreOffice_7.0.6-MSI-example.ps1:
--------------------------------------------------------------------------------
1 | # Install LibreOffice 7.0.6 MSI
2 | # IntuneWin32 MSI Powershell script install example
3 | #
4 | # UI Languages: FI, EN, SE
5 | # Proofing languages: English, German, French, Swedish and Spanish
6 | #
7 | #
8 | # Petri.Paavola@yodamiitti.fi
9 | # Microsoft MVP - Windows and Intune
10 | # 2024-11-01
11 | #
12 | # Original script source:
13 | # https://github.com/petripaavola/Intune/blob/master/Apps/install_LibreOffice_7.0.6-MSI-example.ps1
14 | #
15 | # Intune install command:
16 | # Powershell.exe -ExecutionPolicy Bypass -File install_LibreOffice_7.0.6-MSI-example.ps1
17 | #
18 | # Intune uninstall command:
19 | # msiexec /x {9F9A9C01-5A65-4C2E-A243-FC88C81BC35F} /qn /l*v C:\Windows\Logs\Uninstall_LibreOffice_7.0.6_MSI.log
20 | #
21 | # MSI ProductCode={9F9A9C01-5A65-4C2E-A243-FC88C81BC35F}
22 | #
23 | # This is MSI application install example script for MSI-files
24 | # In this case there is so long parameter that it will not fit in Intune install command line
25 |
26 |
27 | #
28 | #
29 | # # #
30 | # #
31 | #
32 |
33 | # Configure these values
34 | $SoftwareName = "LibreOffice"
35 | $version = "7.0.6"
36 | $MSIFilePath = "$PSScriptRoot\LibreOffice_7.0.6_Win_x64.msi"
37 |
38 | #
39 | # #
40 | # # #
41 | #
42 | #
43 |
44 |
45 |
46 | # Start script in 64bit environment if script was started in 32bit environment
47 | # Intune Win32 application install process starts in 32bit process by default (2024-11-01)
48 | # Few commands will require native bit command to work. Examples pnputil.exe and dism.exe
49 | # Below 64bit workaround is provided by Oliver Kieselbach
50 | # Original example:
51 | # https://github.com/okieselbach/Intune/blob/master/ManagementExtension-Samples/IntunePSTemplate.ps1
52 | if (-not [System.Environment]::Is64BitProcess)
53 | {
54 | # start new PowerShell as x64 bit process, wait for it and gather exit code and standard error output
55 | $sysNativePowerShell = "$($PSHOME.ToLower().Replace("syswow64", "sysnative"))\powershell.exe"
56 |
57 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo
58 | $pinfo.FileName = $sysNativePowerShell
59 | $pinfo.Arguments = "-ex bypass -file `"$PSCommandPath`""
60 | $pinfo.RedirectStandardError = $true
61 | $pinfo.RedirectStandardOutput = $true
62 | $pinfo.CreateNoWindow = $true
63 | $pinfo.UseShellExecute = $false
64 | $p = New-Object System.Diagnostics.Process
65 | $p.StartInfo = $pinfo
66 | $p.Start() | Out-Null
67 | $p.WaitForExit() # Wait for the 64-bit process to complete
68 |
69 | $exitCode = $p.ExitCode
70 |
71 | $stderr = $p.StandardError.ReadToEnd()
72 |
73 | if ($stderr) { Write-Error -Message $stderr }
74 |
75 | exit $exitCode
76 |
77 | } else {
78 |
79 | #############################################################
80 | # Main script starts here
81 |
82 | # Start logging
83 | Start-Transcript "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin32App.log" -Append
84 |
85 |
86 | # Running inside Try so we can catch fatal errors
87 | try {
88 | Write-Host "Install $SoftwareName $version"
89 |
90 |
91 | # Check that $MSIFilePath exists
92 | if (-not (Test-Path $MSIFilePath)) {
93 | Write-Host "Installer file not found: $MSIFilePath"
94 | Write-Host "Exiting installation with error code 9999"
95 | Stop-Transcript
96 | Exit 9999
97 | }
98 |
99 |
100 | # Log file path for stdOut and stdError
101 | $RedirectStandardOutputFilePath = "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin_RedirectStandardOutput.log"
102 | $RedirectStandardErrorFilePath = "C:\Windows\Logs\Install_$($SoftwareName)_$($version)_IntuneWin_RedirectStandardError.log"
103 |
104 | $Installer = "msiexec.exe"
105 | $Arguments = @(
106 | '/i'
107 | "`"$MSIFilePath`""
108 | '/qn'
109 | "/l*v C:\Windows\Logs\Install_$($SoftwareName)_$($version)_MSI.log"
110 | '/norestart'
111 | 'RebootYesNo=No'
112 | 'ALLUSERS=1'
113 | 'CREATEDESKTOPLINK=0'
114 | 'REGISTER_ALL_MSO_TYPES=0'
115 | 'REGISTER_NO_MSO_TYPES=1'
116 | 'ISCHECKFORPRODUCTUPDATES=0'
117 | 'QUICKSTART=0'
118 | 'ADDLOCAL=ALL'
119 | 'UI_LANGS=en_US,sv,fi'
120 | 'REMOVE=gm_r_ex_Dictionary_Af,gm_r_ex_Dictionary_Sq,gm_r_ex_Dictionary_Tr,gm_r_ex_Dictionary_An,gm_r_ex_Dictionary_Ar,gm_r_ex_Dictionary_Be,gm_r_ex_Dictionary_Bg,gm_r_ex_Dictionary_Bn,gm_r_ex_Dictionary_Br,gm_r_ex_Dictionary_Pt_Pt,gm_r_ex_Dictionary_Pt_Br,gm_r_ex_Dictionary_Bs,gm_r_ex_Dictionary_Ca,gm_r_ex_Dictionary_Bo,gm_r_ex_Dictionary_Cs,gm_r_ex_Dictionary_Da,gm_r_ex_Dictionary_Nl,gm_r_ex_Dictionary_Et,gm_r_ex_Dictionary_Gd,gm_r_ex_Dictionary_Gl,gm_r_ex_Dictionary_Gu,gm_r_ex_Dictionary_He,gm_r_ex_Dictionary_Hi,gm_r_ex_Dictionary_Hu,gm_r_ex_Dictionary_Hr,gm_r_ex_Dictionary_Id,gm_r_ex_Dictionary_It,gm_r_ex_Dictionary_Is,gm_r_ex_Dictionary_Lt,gm_r_ex_Dictionary_Lo,gm_r_ex_Dictionary_Lv,gm_r_ex_Dictionary_Ne,gm_r_ex_Dictionary_No,gm_r_ex_Dictionary_Oc,gm_r_ex_Dictionary_Pl,gm_r_ex_Dictionary_Ro,gm_r_ex_Dictionary_Ru,gm_r_ex_Dictionary_Sr,gm_r_ex_Dictionary_Si,gm_r_ex_Dictionary_Sk,gm_r_ex_Dictionary_Sl,gm_r_ex_Dictionary_El,gm_r_ex_Dictionary_Te,gm_r_ex_Dictionary_Th,gm_r_ex_Dictionary_Uk,gm_r_ex_Dictionary_Vi,gm_r_ex_Dictionary_Zu'
121 | )
122 |
123 |
124 | # We use Start-Process which gets most probability the ExitCode
125 | # Some really really rare cases $LastExitCode does not work, so this works better
126 | $Process = Start-Process $Installer -ArgumentList $Arguments -Wait -Passthru -NoNewWindow -RedirectStandardOutput $RedirectStandardOutputFilePath -RedirectStandardError $RedirectStandardErrorFilePath
127 | $Process.WaitForExit()
128 | $ExitCode = $Process.ExitCode
129 |
130 | if ($ExitCode -eq 0) {
131 | # MSI installation was successful
132 |
133 | Write-Host "$SoftwareName $version installation succeeded with ExitCode $ExitCode"
134 | Stop-Transcript
135 | exit $ExitCode
136 |
137 | } elseif ($ExitCode -eq 3010) {
138 | # MSI installation was successful but reboot is required after software install
139 |
140 | Write-Host "$SoftwareName $version installed successfully but requires a reboot (ExitCode: $ExitCode)"
141 | Stop-Transcript
142 | exit $ExitCode
143 |
144 | } else {
145 | # MSI installation failed
146 |
147 | Write-Host "Error installing $SoftwareName. ExitCode: $ExitCode"
148 | Write-Host "Exiting installation with ExitCode $ExitCode"
149 | Stop-Transcript
150 | Exit $ExitCode
151 | }
152 | } catch {
153 | # Handle any unexpected errors
154 |
155 | Write-Host "An unexpected fatal error occurred: $_"
156 | Write-Host "Exiting installation with ExitCode 99999"
157 | Stop-Transcript
158 | Exit 99999
159 |
160 | }
161 | }
--------------------------------------------------------------------------------
/AutopilotBranding-Yodamiitti/README.md:
--------------------------------------------------------------------------------
1 | Petri's AutopilotBranding script will be here.
2 |
3 | There were some interesting findings in "all" AutopilotBranding scripts so there's a little delay until I can release current version to here.
4 |
--------------------------------------------------------------------------------
/CVE-2023-24932/BlackLotus-Intune-Remediation-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/CVE-2023-24932/BlackLotus-Intune-Remediation-screenshot.png
--------------------------------------------------------------------------------
/CVE-2023-24932/Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-1of3-Check if new 2023 certificate is installed to UEFI db.ps1:
--------------------------------------------------------------------------------
1 | # BlackLotus CVE-2023-24932 mitigation check 1/3
2 | # Check if Windows UEFI CA 2023 certificate is installed to UEFI database
3 | #
4 | # Petri.Paavola@yodamiitti.fi
5 | # Microsoft MVP - Windows and Devices
6 | # 24.5.2024
7 | #
8 | # Read Microsoft mitigation instructions from
9 | # https://support.microsoft.com/en-au/topic/kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932-41a975df-beb2-40c1-99a3-b3ff139f832d
10 |
11 |
12 | # Check if Windows UEFI CA 2023 certificate is installed in UEFI database
13 | $NewUEFICertificateInstalled = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI db).bytes) -match 'Windows UEFI CA 2023'
14 |
15 | if($NewUEFICertificateInstalled) {
16 | # Windows UEFI CA 2023 certificate is installed to UEFI database
17 |
18 | Write-Host "Windows UEFI CA 2023 certificate is installed to UEFI database"
19 | Exit 0
20 |
21 | } else {
22 | # Windows UEFI CA 2023 certificate is NOT installed to UEFI database
23 |
24 | Write-Host "Windows UEFI CA 2023 certificate is NOT installed to UEFI database"
25 | Exit 1
26 | }
27 |
--------------------------------------------------------------------------------
/CVE-2023-24932/Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-2of3-Check if new 2023 certificate is installed to Windows Boot Manager.ps1:
--------------------------------------------------------------------------------
1 | # BlackLotus CVE-2023-24932 mitigation check 2/3
2 | #
3 | # Check if Windows UEFI CA 2023 certificate
4 | # is installed to current running Windows boot manager
5 | #
6 | # Petri.Paavola@yodamiitti.fi
7 | # Microsoft MVP - Windows and Devices
8 | # 24.5.2024
9 | #
10 | # Read Microsoft mitigation instructions from
11 | # https://support.microsoft.com/en-au/topic/kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932-41a975df-beb2-40c1-99a3-b3ff139f832d
12 | #
13 | #
14 | # This is heavily influenced by GaryTown script :) Thanks!
15 | # https://github.com/gwblok/garytown/blob/master/ConfigMgr/Baselines/CVE-2023-24932/KB5025885-CheckCompliance.ps1
16 | #
17 | #
18 | # You may also want to read GaryTown blogs about this issue
19 | # https://garytown.com/configmgr-task-sequence-kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932
20 | #
21 | #
22 |
23 | # Get EFI folder path (without drive letter)
24 | $Volume = Get-Volume | Where-Object {$_.FileSystemType -eq "FAT32" -and $_.DriveType -eq "Fixed"}
25 | $SystemDisk = Get-Disk | Where-Object {$_.IsSystem -eq $true}
26 | $SystemPartition = Get-Partition -DiskNumber $SystemDisk.DiskNumber | Where-Object {$_.IsSystem -eq $true}
27 | $SystemVolume = $Volume | Where-Object {$_.UniqueId -match $SystemPartition.Guid}
28 | $FilePath = "$($SystemVolume.Path)\EFI\Microsoft\Boot\bootmgfw.efi"
29 |
30 | # Get bootmgfw.efi Boot Manager certificates
31 | $CertCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
32 | $CertCollection.Import($FilePath, $null, 'DefaultKeySet')
33 |
34 |
35 | If ($CertCollection.Subject -like "*Windows UEFI CA 2023*") {
36 | # Windows UEFI CA 2023 certificate is installed to current running Windows Boot Manager
37 |
38 | Write-Host "Windows UEFI CA 2023 certificate is installed to current running Windows Boot Manager"
39 | Exit 0
40 |
41 | } else {
42 | # Windows UEFI CA 2023 certificate is NOT installed to current running Windows Boot Manager
43 |
44 | Write-Host "Windows UEFI CA 2023 certificate is NOT installed to current running Windows Boot Manager"
45 | Exit 1
46 | }
47 |
--------------------------------------------------------------------------------
/CVE-2023-24932/Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-3of3-Check if old compromised 2011 certificate is revocaked in UEFI.ps1:
--------------------------------------------------------------------------------
1 | # BlackLotus CVE-2023-24932 mitigation check 3/3
2 | # Check if compromised old Microsoft Windows Production PCA 2011
3 | # certificate is revocated in UEFI database
4 | #
5 | # Petri.Paavola@yodamiitti.fi
6 | # Microsoft MVP - Windows and Devices
7 | # 24.5.2024
8 | #
9 | # Read Microsoft mitigation instructions from
10 | # https://support.microsoft.com/en-au/topic/kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932-41a975df-beb2-40c1-99a3-b3ff139f832d
11 |
12 |
13 | # Check if compromised old Microsoft Windows Production PCA 2011 certificate
14 | # is added to UEFI certificate revocation database
15 | $CompromisedOldUEFICertificateRevocated = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI dbx).bytes) -match 'Microsoft Windows Production PCA 2011'
16 |
17 | if($CompromisedOldUEFICertificateRevocated) {
18 | # Compromised old Windows Production PCA 2011 certificate is revocated in UEFI certificate database
19 |
20 | Write-Host "Compromised old Windows Production PCA 2011 certificate is revocated in UEFI certificate database"
21 | Exit 0
22 |
23 | } else {
24 | # Compromised old Windows Production PCA 2011 certificate is NOT revocated in UEFI certificate database
25 |
26 | Write-Host "Compromised old Windows Production PCA 2011 certificate is NOT revocated in UEFI certificate database"
27 | Exit 1
28 | }
29 |
--------------------------------------------------------------------------------
/CVE-2023-24932/readme.md:
--------------------------------------------------------------------------------
1 | # CVE-2023-24932 - UEFI Secure Boot BlackLotus mitigations and reporting #
2 |
3 | ### Read Microsoft mitigation instructions ###
4 | [KB5025885: How to manage the Windows Boot Manager revocations for Secure Boot changes associated with CVE-2023-24932](https://support.microsoft.com/en-au/topic/kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932-41a975df-beb2-40c1-99a3-b3ff139f832d)
5 |
6 | ### Does this affect me? ###
7 | **Yes**
8 |
9 | ### Short summary what is happening ###
10 | * BlackLotus Bootkit bypasses for example Secure Boot, Bitlocker drive encryption and Windows Defender
11 | * All Windows devices will be updated automatically by Microsoft
12 | * Process goes in 3 phases
13 | * Phase 1/3: **Secure Boot certificate needs to be updated in UEFI database**
14 | * Phase 2/3: **Windows Bootloaders will need to be signed with new certificate**
15 | * Phase 3/3: **Old Secure Boot certificate will need to be revoked from UEFI Secure Boot database**
16 | * **After 3rd phase all non updated boot medias will not** start because old Secure Boot certificate is recovated
17 | * This includes ConfigMgr/MDT PXE, WinPE, USB-drives, .ISO -images, OEM install medias, backup and recovery images, pretty much everything
18 | * This is the phase when you need to update your existing deployment methods (PXE, WinPE, USB, .ISO, etc...)
19 | * Windows devices are not trusting old bootloaders after 3rd phase so install medias need to be updated (unless Secure Boot is disabled for temporary recovery purposes)
20 | * Mitigations for Windows devices will start after July 9th
21 | * We are still waiting official instructions from Microsoft how to update all PXE, WinPE, USB and other medias
22 | * There is information but not yet clear path and instructions what to do and when
23 |
24 | ### Monitor mitigations with Intune Remediation ###
25 |
26 |
27 |
28 | **I have created 3 simple Intune remediation *Detection* scripts *to monitor* mitigations in your environment.**
29 |
30 | These scripts are only for monitoring (at least for now). They will not make any changes as this whole case is kind of tricky one to mitigate.
31 |
32 | ### Mitigation phase 1/3 report ###
33 | Name: **CVE-2023-24932 mitigation check 1/3 - Is Windows UEFI CA 2023 certificate installed to UEFI db**
34 | [Download Remediation detection script 1/3](./Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-1of3-Check%20if%20new%202023%20certificate%20is%20installed%20to%20UEFI%20db.ps1)
35 |
36 |
37 |
38 | ### Mitigation phase 2/3 report ###
39 | Name: **CVE-2023-24932 mitigation check 2/3 - Is Windows UEFI CA 2023 certificate installed to Windows Boot Manager (bootmgfw.efi)**
40 | [Download Remediation detection script 2/3](./Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-2of3-Check%20if%20new%202023%20certificate%20is%20installed%20to%20Windows%20Boot%20Manager.ps1)
41 |
42 |
43 |
44 | ### Mitigation phase 3/3 report ###
45 | Name: **CVE-2023-24932 mitigation check 3/3 - Is deprecated old Microsoft Windows Production PCA 2011 certificate revocated in UEFI db**
46 | [Download Remediation detection script 3/3](./Intune-Remediation-Detection-BlackLotus-CVE-2023-24932-3of3-Check%20if%20old%20compromised%202011%20certificate%20is%20revocaked%20in%20UEFI.ps1)
47 |
48 | **This is where the magic or pain happens. All non updated install medias and non updated Windows OSs will fail to load after this mitigation**
49 |
50 | #### Configure script options ####
51 | Run this script using the logged-on credentials: **No**
52 | Enforce script signature check: **No**
53 | Run script in 64-bit PowerShell: **Yes**
54 |
55 | ### Mitigations to PXE, WinPE and custom medias ###
56 | There are instructions how to do these. For now I'll leave this as placeholder and update when we get Microsoft official steps how to update offline-medias.
57 |
58 | ### Related blogs ###
59 | There are many other blogs and scripts for this topic which your favorite search engine will find.
60 |
61 | One of those are GaryTown's blogs and scripts
62 | [GaryTown Blog](https://garytown.com/configmgr-task-sequence-kb5025885-how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932)
63 | [GaryTown GitHub](https://github.com/gwblok/garytown/tree/master/ConfigMgr/Baselines/CVE-2023-24932)
64 |
--------------------------------------------------------------------------------
/Device_Query/readme.MD:
--------------------------------------------------------------------------------
1 | ## Table of Contents
2 |
3 | ### Intune Device Query examples
4 | - [Event logs Device Query](#event-logs-device-query)
5 | - [Registry Device Query](#registry-device-query)
6 | - [Files Device Query](#files-device-query)
7 | - [Certificates Device Query](#certificates-device-query)
8 | - [Process Device Query](#process-device-query)
9 | - [EncryptableVolume Device Query](#encryptablevolume-device-query)
10 | - [LocalUserAccount Device Query](#localuseraccount-device-query)
11 | - [WindowsDriver Device Query](#windowsdriver-device-query)
12 | - [WindowsService Device Query](#windowsservice-device-query)
13 |
14 | ### Intune Multi Device Query examples
15 | - [Battery](#Battery)
16 | - [BiosInfo](#BiosInfo)
17 | - [Cpu](#Cpu)
18 | - [Tpm](#Tpm)
19 | - [WindowsQfe](#windowsqfe)
20 |
21 |
22 | # Intune Device Query examples
23 |
24 | ## Event logs Device Query
25 | ```
26 | // Get Intune MDM Management policy errors
27 | WindowsEvent('Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin',12h)
28 | | where EventId == 404 and Message !contains "Fake"
29 | | project LoggedDateTime, EventId, Message
30 | ```
31 | ```
32 | // Get MSI Application installations from last 7 days
33 | WindowsEvent('Application', 7d)
34 | | where Message contains 'Installation completed successfully'
35 | | project LogName, EventId , Message, LoggedDateTime
36 | ```
37 | ```
38 | // Check for Windows 11 24H2 2025-01 Cumulative Update installation steps and times
39 | // You can see when CU was installed and when computer was restarted -> Staged state changed to Installed State
40 | WindowsEvent('Setup', 30d)
41 | | where Message contains "KB5050009" and (EventId == 1 or EventId == 4 or EventId == 2)
42 | | project LoggedDateTime, Message, EventId
43 | | order by LoggedDateTime desc
44 | ```
45 | ```
46 | // Get Bitlocker events from last 1 days
47 | WindowsEvent('Microsoft-Windows-Bitlocker/Bitlocker Management',1d)
48 | | order by LoggedDateTime
49 | ```
50 | ```
51 | // Get Bitlocker Errors and Warnings from last 7 days
52 | WindowsEvent('Microsoft-Windows-Bitlocker/Bitlocker Management',7d)
53 | | order by LoggedDateTime
54 | | where Level == 'ERROR' or Level == 'WARNING'
55 | ```
56 | ```
57 | // Get AppLocker warning for EXE and DLL
58 | WindowsEvent('Microsoft-Windows-AppLocker/EXE and DLL',7d)
59 | | where Level == 'Warning'
60 | ```
61 | ```
62 | // Get AppLocker warning for MSIs and scripts
63 | WindowsEvent('Microsoft-Windows-AppLocker/MSI and Script',1d)
64 | | where Level == 'Warning'
65 | ```
66 | ```
67 | // Get Boot times in last 40 days
68 | WindowsEvent('System',40d)
69 | | where EventId == 6005
70 | | order by LoggedDateTime
71 | | project LoggedDateTime, Message
72 | ```
73 |
74 |
75 | ## Registry Device Query
76 |
77 | ```
78 | // Get IP-address from registry
79 | WindowsRegistry('HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\*')
80 | | where ValueName contains 'IPaddress' and ValueData != '0.0.0.0'
81 | | project ValueName, ValueData
82 | ```
83 | ```
84 | // Get Defender for Endpoint DeviceTag value from registry
85 | WindowsRegistry('HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection\DeviceTagging')
86 | | where ValueName == 'Group'
87 | | project ValueData
88 | ```
89 | ```
90 | // Get Chrome policies registry keys
91 | WindowsRegistry('HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\*')
92 | | project RegistryKey, ValueName, ValueType, ValueData
93 | ```
94 | ```
95 | WindowsRegistry('HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender\Policy Manager')
96 | | where ValueData == '0'
97 | ```
98 |
99 | ## Files Device Query
100 | ```
101 | // Get cmd.exe file information
102 | FileInfo('c:\\windows\\system32\\cmd.exe')
103 | | project FileName, Directory, ProductVersion, DateTime=substring(tostring(CreatedDateTime), 0, 10)
104 | ```
105 | ```
106 | // Get all files and subfiles and subfolders in C:\Windows\Logs
107 | FileInfo('C:\Windows\Logs')
108 | ```
109 | ```
110 | // Get all user profiles in computer - workaround
111 | FileInfo('c:\users\*\ntuser.ini')
112 | | project User=substring(Directory,9,)
113 | | where User != 'Administrator' and User !contains 'default' and User !contains '$'
114 | | order by User asc
115 | ```
116 |
117 | ## Certificates Device Query
118 |
119 | ```
120 | // Get Intune Suite CloudPKI certificate
121 | Certificate
122 | | where CommonName startswith 'CloudPKI'
123 | | project CommonName, ValidToDateTime, SelfSigned, SigningAlgorithm, KeyStrength
124 | ```
125 | ```
126 | // Get DigiCert issuer certificates
127 | Certificate
128 | | where Issuer contains 'DigiCert'
129 | | project CommonName, ValidToDateTime, SelfSigned, SigningAlgorithm, KeyStrength
130 | ```
131 |
132 | ## Process Device Query
133 | ```
134 | // Get explorer.exe process information
135 | Process
136 | | where ProcessName == 'explorer.exe'
137 | | project ProcessId, ProcessName, Path, TotalSizeBytes
138 | ```
139 |
140 | ## EncryptableVolume Device Query
141 | ```
142 | EncryptableVolume
143 | | join Tpm
144 | | project WindowsDriveLetter, ProtectionStatus, EncryptionMethod, EncryptionPercentage, Activated, Enabled, SpecVersion, Manufacturer
145 | | where ProtectionStatus == 'protected'
146 | ```
147 |
148 | ## LocalUserAccount Device Query
149 | ```
150 | // List all local user accounts
151 | LocalUserAccount
152 | | project UserId, Username, HomeDirectory, WindowsSid
153 | ```
154 |
155 | ## WindowsDriver Device Query
156 |
157 | ```
158 | // Get Microsoft drivers
159 | WindowsDriver
160 | | where DriverDescription contains 'Microsoft'
161 | ```
162 |
163 | ## WindowsService Device Query
164 |
165 | ```
166 | // Get Windows services which have StartMode Automatic but current state is stopped
167 | WindowsService
168 | | where StartMode == "AUTO"
169 | | where State == "STOPPED"
170 | ```
171 |
172 | # Intune Multi Device Query examples
173 |
174 | ## Battery
175 | ```
176 | // Get devices Battery CycleCount and sort CycleCount descending and show only few properties (with project)
177 | Battery
178 | | project Device, CycleCount, DesignedCapacity, FullChargedCapacity
179 | | order by CycleCount desc
180 | ```
181 | ```
182 | // Get devices where Battery FullChargeCapacity is lower than DesignedCapacity.
183 | // This tells us if Battery is not 100% anymore
184 | Battery
185 | | where FullChargedCapacity < DesignedCapacity
186 | | order by CycleCount desc
187 | ```
188 |
189 | ## BiosInfo
190 | ```
191 | // Get BiosInfo for HP devices where BIOS information is not the latest version (P78 Ver. 01.49)
192 | BiosInfo
193 | | where Manufacturer == "HP" and SmBiosVersion != "P78 Ver. 01.49"
194 | ```
195 |
196 | ## Cpu
197 | ```
198 | // Get ARM64 devices using exact operator
199 | Cpu
200 | | where Architecture == "ARM64"
201 | ```
202 | ```
203 | // Get ARM devices using startswith operator
204 | Cpu
205 | | where Architecture startswith "ARM"
206 | ```
207 | ```
208 | // Get ARM devices using contains operator
209 | Cpu
210 | | where Architecture contains "ARM"
211 | ```
212 |
213 | ## Tpm
214 | ```
215 | // Get devices where TPM is NOT in desired state
216 | Tpm
217 | | where Activated == false or Enabled == false or Owned == false
218 | ```
219 | ```
220 | // Get devices where TPM IS Activated
221 | Tpm
222 | | where Activated == true
223 | ```
224 | ```
225 | // Get devices where TPM is NOT Activated
226 | Tpm
227 | | where Activated == false
228 | ```
229 |
230 |
231 | ## WindowsQfe
232 | ```
233 | // Get 2025-04 Cumulative Update for W11 23H2 and W11 24H2
234 | WindowsQfe
235 | | where HotFixId == "KB5055528" or HotFixId == "KB5055523"
236 | ```
237 |
--------------------------------------------------------------------------------
/Devices/Intune_Device_SendCustomNotification.ps1:
--------------------------------------------------------------------------------
1 | # Send Custom Notification to Intune Device
2 | #
3 | # Example:
4 | # .\Intune_Device_SendCustomNotification.ps1 -id 034edbcd-a295-4124-829a-e15b50bd003a -MessageTitle "Important message!" -MessageBody "Hello World!"
5 | #
6 | # You can pass device object in pipeline. id is used automatically
7 | # Get-DeviceManagement_ManagedDevices -Filter "deviceName eq 'etunimi.sukunimi_AndroidForWork_9/19/2019_12:10 PM'" | .\Intune_Device_SendCustomNotification.ps1 -MessageTitle "Important message!" -MessageBody "Hello World!"
8 | #
9 | # TODO: Proper processing if multiple deviceObjects are passed from pipeline
10 | #
11 | # Petri.Paavola@yodamiitti.fi
12 | # 24.10.2019
13 |
14 | Param(
15 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [String]$id,
16 | [Parameter(Mandatory = $true, Position = 1)] [String]$MessageTitle,
17 | [Parameter(Mandatory = $true, Position = 2)] [String]$MessageBody
18 | )
19 |
20 |
21 | <# Request body JSON syntax
22 | {
23 | "notificationTitle":"Message from GraphAPI",
24 | "notificationBody":"Hello World!"
25 | }
26 | #>
27 |
28 | # Create Powershell custom object which will be converted to JSON
29 | $TargetObject = $null
30 | $TargetObject = New-Object PSObject
31 | $TargetObject | Add-Member NoteProperty 'notificationTitle' $MessageTitle
32 | $TargetObject | Add-Member NoteProperty 'notificationBody' $MessageBody
33 |
34 | # Create json from Powershell custom object
35 | # Cast variable as String, otherwise Invoke-MSGraphRequest will fail
36 | # This is really important step!
37 | [String]$SendMessageBodyJSON = $TargetObject | ConvertTo-json
38 |
39 | # Debug
40 | #$SendMessageBodyJSON
41 |
42 | # Device Custom Notification Message url
43 | $url = "https://graph.microsoft.com/beta/deviceManagement/managedDevices/$id/sendCustomNotificationToCompanyPortal"
44 |
45 | # Send MSGraph request
46 | Invoke-MSGraphRequest -Url $url -Content $SendMessageBodyJSON -HttpMethod 'POST'
47 | $Success = $?
48 |
49 | Write-Output "Send Device (id:$id) Notification success: $Success"
50 |
--------------------------------------------------------------------------------
/Devices/README.md:
--------------------------------------------------------------------------------
1 | # Intune Powershell Native Cmdlet examples and scripts
2 |
--------------------------------------------------------------------------------
/Groups/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Linux/Intune_Linux_Custom_Compliance_script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Intune Linux Custom Compliance script
4 | # This script runs actual compliance check as Powershell script
5 | # which is included in this script
6 | #
7 | # Compliance checks:
8 | # Check Powershell is installed
9 | # Reboot Required check (file should not exist /var/run/reboot-required)
10 | # Check MS Edge is installed (file should exist /opt/microsoft/msedge/msedge
11 | # Check MS Edge version
12 | # Check Powershell version
13 | # Check Kernel version
14 | # Check Kernel patch level
15 | # Check Kernel flavour
16 | # Check Kernel tainted state
17 | # Check SecureBoot status
18 | # Check sysctrl values (placeholder to check any value)
19 | # user.max_user_namespaces
20 | # Check gsettings values (placeholder to check any value)
21 | # org.gnome.desktop.screensaver lock-enabled
22 | # org.gnome.desktop.screensaver idle-activation-enabled
23 | # org.gnome.desktop.session idle-delay
24 | # Check Defender for Endpoint on Linux status
25 | # MicrosoftDefenderForEndpointOnLinux_Installed
26 | # MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization
27 | # MicrosoftDefenderForEndpointOnLinux_Healthy
28 | # MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date
29 | # MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled
30 | #
31 | #
32 | # Script creates 2 log files for debugging (which you may want to disable in production?)
33 | # /tmp/IntuneCustomComplianceScript_Bash.log
34 | # /tmp/IntuneCustomComplianceScript_Powershell.log
35 | #
36 | # Note: Make sure you have installed Powershell before running this script
37 | #
38 | # Note: make sure you are NOT putting anything
39 | # to STDOUT because that breaks the script
40 | # Powershell script returns compressed JSON to STDOUT
41 | # which is passed to Intune for compliance validation check
42 | #
43 | #
44 | # Petri.Paavola@yodamiitti.fi
45 | # Microsoft MVP - Windows and Devices for IT
46 | # 15.3.2023
47 | #
48 | # https://github.com/petripaavola/Intune/tree/master/Linux
49 |
50 |
51 | Version=3.0
52 | BashLogFilePath=/tmp/IntuneCustomComplianceScript_Bash.log
53 |
54 | # Create log for debugging
55 | echo "Intune Linux Custom Compliance bash script version $Version" > $BashLogFilePath
56 | echo "Runtime: $(/bin/date +%Y-%m-%d_%H:%M:%S)" >> $BashLogFilePath
57 | echo "Running script: $0" >> $BashLogFilePath
58 | echo "Running as user: $( /bin/whoami; )" >> $BashLogFilePath
59 | echo "Working directory: $( /bin/pwd; )" >> $BashLogFilePath
60 |
61 | # Create Powershell commands string
62 | PowerShellCommandsString='
63 | # Note: Do not use single quotes in this Powershell script
64 | # because it will break the bash script
65 | #
66 | # Note: Start-Transcript breaks the script
67 | # Note: using Write-Host causes unnecessary data returned to bash script and may break the script
68 | # Note: using Write-Output breaks the script
69 |
70 | $ScriptStartTime = Get-Date
71 |
72 | # Create hash for JSON
73 | # We will add values
74 | $hash = @{}
75 |
76 | # Add PowershellInstalled=$True
77 | $hash.Add("PowershellInstalled", $True)
78 |
79 | # Create logData array
80 | $logData = @()
81 | $logData += "###################################################"
82 | $logData += "Starting Intune Linux Custom Compliance script Powershell`n"
83 |
84 | $RunTime = Get-Date -Format yyyyMMdd-HHmmss
85 | $logData += "RunTime = $RunTime"
86 |
87 | $Whoami = whoami
88 | $logData += "Whoami = $Whoami"
89 |
90 | $CurrentPath = Get-Location | Select-Object -ExpandProperty Path
91 | $logData += "CurrentPath = $CurrentPath"
92 |
93 |
94 | ####################################################################################
95 | # Check reboot pending
96 |
97 | $logData += "###################################################"
98 | $logData += "Run Reboot Pending compliance check`n"
99 | $logData += "file should not exist /var/run/reboot-required"
100 | if(Test-Path /var/run/reboot-required) {
101 | $RebootRequired = $True
102 | } else {
103 | $RebootRequired = $False
104 | }
105 | $logData += "RebootRequired=$RebootRequired"
106 | $hash.Add("RebootRequired", $RebootRequired)
107 |
108 |
109 | ####################################################################################
110 | # Check MSEdge is installed
111 |
112 | $logData += "###################################################"
113 | $logData += "Run Microsoft Edge is installed check`n"
114 | $logData += "file should exist /opt/microsoft/msedge/msedge"
115 | if(Test-Path /opt/microsoft/msedge/msedge) {
116 | $MSEdgeInstalled = $True
117 | } else {
118 | $MSEdgeInstalled = $False
119 | }
120 | $logData += "MSEdgeInstalled=$MSEdgeInstalled"
121 | $hash.Add("MSEdgeInstalled", $MSEdgeInstalled)
122 |
123 |
124 | ####################################################################################
125 | # Check MSEdge version
126 |
127 | $logData += "###################################################"
128 | $logData += "Run Microsoft Edge version check`n"
129 | $logData += "with command /opt/microsoft/msedge/msedge --version"
130 |
131 | $MSEdgeVersionString = /opt/microsoft/msedge/msedge --version
132 | # Example string which can be tested with regex101.com
133 | # Microsoft Edge 109.0.1518.70 unknown
134 | $regex = "^.* ([0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}).*"
135 | if($MSEdgeVersionString -match $regex) {
136 | $MSEdgeVersion = $Matches[1]
137 | } else {
138 | # Regex did not find MSEdge version
139 | $MSEdgeVersion = "0.0.0.0"
140 | }
141 |
142 | $logData += "MSEdgeVersion=$MSEdgeVersion"
143 | $hash.Add("MSEdgeVersion", $MSEdgeVersion)
144 |
145 |
146 | ####################################################################################
147 | # Check Powershell version
148 |
149 | $logData += "###################################################"
150 | $logData += "Run Powershell version check`n"
151 | [String]$PSVersion = $PSVersionTable | Select-Object -ExpandProperty PSVersion
152 | $logData += "PSVersion=$PSVersion"
153 | $hash.Add("PSVersion", $PSVersion)
154 |
155 |
156 | ####################################################################################
157 | # Check Kernel version, patch level and flavour
158 |
159 | $logData += "###################################################"
160 | $logData += "Run Kernel version check`n"
161 | # Example kernel version: 5.15.0-52-generic
162 | # Kernel version is: 5.15.0
163 | # Patch level is: 52
164 | # Linux distro/kernel specific additional info: generic
165 |
166 | # Get kernel information
167 | $KernelVersionUname = & /bin/uname -r
168 |
169 | # Extract version info to own variables
170 | $KernelVersion = ($KernelVersionUname.Split("-"))[0]
171 | $logData += "KernelVersion=$KernelVersion"
172 | $hash.Add("KernelVersion", $KernelVersion)
173 |
174 | $KernelPatchLevel = ($KernelVersionUname.Split("-"))[1]
175 | # Add .0 to kernel patch level version 52 -> 52.0
176 | # Otherwise our compliance JSON will no work correctly for Version comparison
177 | $KernelPatchLevel = "$($KernelPatchLevel).0"
178 | $logData += "KernelPatchLevel=$KernelPatchLevel"
179 | $hash.Add("KernelPatchLevel", $KernelPatchLevel)
180 |
181 | $KernelFlavour = ($KernelVersionUname.Split("-"))[2]
182 | $logData += "KernelFlavour=$KernelFlavour"
183 | $hash.Add("KernelFlavour", $KernelFlavour)
184 |
185 |
186 | ####################################################################################
187 | # Check Kernel tainted state
188 | #
189 | # There is a lot going on with tainted kernel situation
190 | # Kernel could be in tainted mode intentionally
191 | # when using for example proprietary NVIDIA or AMD graphics drivers
192 | # But many times tainted kernel state is causing bigger problems
193 | # https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html#decoding-tainted-state-at-runtime
194 | # Fix for tainted kernel is to reboot the device
195 | # Value 0 is normal when kernel is NOT tainted
196 |
197 | $logData += "###################################################"
198 | $logData += "Run Kernel tainted state check`n"
199 |
200 | [int]$TaintedKernelState = Get-Content /proc/sys/kernel/tainted
201 | $logData += "/proc/sys/kernel/tainted"
202 | $logData += "$TaintedKernelState"
203 |
204 | $logData += "TaintedKernelState = $TaintedKernelState"
205 | $hash.Add("TaintedKernelState", $TaintedKernelState)
206 |
207 |
208 | ####################################################################################
209 | # Check SecureBoot status
210 |
211 | $logData += "###################################################"
212 | $logData += "Check SecureBoot status`n"
213 |
214 | $SecureBootStatus = & /bin/mokutil --sb-state
215 | $logData += "/bin/mokutil --sb-state result"
216 | $logData += "$SecureBootStatus"
217 |
218 | # SecureBoot status can be:
219 | # SecureBoot enabled
220 | # SecureBoot disabled
221 | if($SecureBootStatus -eq "SecureBoot enabled") {
222 | $SecureBootEnabled = $True
223 | } else {
224 | $SecureBootEnabled = $False
225 | }
226 | $logData += "SecureBootEnabled = $SecureBootEnabled"
227 | $hash.Add("SecureBootEnabled", $SecureBootEnabled)
228 |
229 |
230 | ####################################################################################
231 | # Get sysctl values
232 | # This is another pandoras box of values
233 | # Either disable this step, include only necessary values or pass on all the values
234 | # If you pass all the values then you can later deside what values to check in JSON
235 | # Note: passing all values might slow Compliance check
236 |
237 | $logData += "###################################################"
238 | $logData += "Get sysctl values and convert to hashtable`n"
239 |
240 | # Get sysctrl values
241 | $sysctlArray = & /sbin/sysctl -a
242 |
243 | $sysctlValuesAddedToHash = 0
244 |
245 |
246 | #
247 | #
248 | # # #
249 | ###
250 | #
251 |
252 | # Process only specified sysctl settings
253 | $sysctlArray = $sysctlArray | Where-Object {
254 | $_ -like "user.max_user_namespaces*" -or
255 | $_ -like "net.ipv4.conf.all.accept_redirects*"
256 | }
257 |
258 | #
259 | ###
260 | # # #
261 | #
262 | #
263 |
264 |
265 | # Process all sysctl values except ignore specified values
266 | # Ignore dev.cdrom.info -values because there are many values with overlapping name
267 | #$sysctlArray = $sysctlArray | Where-Object { $_ -notlike "dev.cdrom.info*"}
268 |
269 |
270 | # Create hash table from values
271 | foreach($sysctl in $sysctlArray) {
272 | $sysctlValueName = $null
273 | $sysctlValue = $null
274 | $Value = $null
275 |
276 | # Validate and extract data with regex
277 | # You can test and validate regex in https://regex101.com
278 | # String: user.max_user_namespaces = 15233
279 | # Regex: ^(.*) = (.*)$
280 |
281 | $regex = "^(.*) = (.*)$"
282 | $Success = $sysctl -match $regex
283 |
284 | # Continue only if regex was match
285 | if($Success) {
286 | # Extract values
287 | $sysctlValueName = "sysctl $($Matches[1])"
288 | $sysctlValue = ($Matches[2]).Trim()
289 |
290 | if($sysctlValue) {
291 | # We have name and value
292 |
293 | # Trim empty spaces
294 | $sysctlValue = $sysctlValue.Trim()
295 |
296 | $logData += "`$sysctlValue=$sysctlValue"
297 | $logData += "GetType `$sysctlValue=$($sysctlValue.GetType())"
298 |
299 | # Check if value is integer
300 | if(($sysctlValue -is [Int32]) -or ($sysctlValue -is [Int64]) -or ($sysctlValue -is [uint32])) {
301 | $Value = [int64]$sysctlValue
302 | $logData += "`$Value=$Value is integer"
303 | } else {
304 | # Cast value as String
305 | $Value = [String]$sysctlValue
306 | $logData += "`$Value=$Value is string"
307 | }
308 |
309 | } else {
310 | # We have name but value is null
311 | $Value = $null
312 | }
313 |
314 | # Add hash key/value if it does not already exist
315 | # This prevents us getting unnecessary errors which could fail this bash script
316 | # and help debugging possible problems
317 | if(-not ($hash.ContainsKey($sysctlValueName))) {
318 | $hash.Add($sysctlValueName, $Value)
319 |
320 | $sysctlValuesAddedToHash++
321 | } else {
322 | $logData += "Skipped duplicate hash valueName: $sysctlValueName"
323 | }
324 |
325 | } else {
326 | # sysctl line did not match our regex
327 | # Print value to log for debugging
328 | $logData += "Did not match regex: $sysctl"
329 | }
330 | }
331 |
332 | $logData += "Added $sysctlValuesAddedToHash sysctl values to hash"
333 |
334 |
335 | ####################################################################################
336 | # Get gsettings
337 |
338 | # Configure below gsettings to be included in compliance check
339 |
340 | #
341 | #
342 | # # #
343 | ###
344 | #
345 |
346 | # Add only selected settings to $hash
347 | $SelectedGsettingsArray = @(
348 | "gsettings get org.gnome.desktop.screensaver lock-enabled",`
349 | "gsettings get org.gnome.desktop.screensaver idle-activation-enabled",`
350 | "gsettings get org.gnome.desktop.session idle-delay"
351 | )
352 |
353 | #
354 | ###
355 | # # #
356 | #
357 | #
358 |
359 | $logData += "###################################################"
360 | $logData += "Get gsettings`n"
361 |
362 | ### function start ###
363 | function Add_gsettingsArrayToHash {
364 | Param(
365 | [Parameter(Mandatory=$true,
366 | ValueFromPipeline=$true,
367 | Position=0)]
368 | $gsettingsArray
369 | )
370 |
371 | $gsettingsHash = @{}
372 |
373 | foreach($gsetting in $gsettingsArray) {
374 | $gsettingName = $null
375 | $gsettingValue = $null
376 | $Value = $null
377 |
378 | # gsettings are either boolean, uint32, integer or string types (all else are strings)
379 | # org.gnome.desktop.screensaver idle-activation-enabled true
380 | # org.gnome.desktop.screensaver lock-delay uint32 0
381 | # org.gnome.desktop.screensaver picture-opacity 100
382 | # org.gnome.desktop.screensaver picture-options singlequotezoomsinglequote
383 | # org.gnome.desktop.search-providers disabled $as []
384 |
385 |
386 | # org.gnome.desktop.screensaver lock-delay uint32 0
387 | # Get uint32 regex match lines
388 | if($gsetting -match "^(.*) (.*) uint32 (.*)$") {
389 | # setting is uint32 type
390 | $gsettingName = "gsettings get $($matches[1]) $($matches[2])"
391 | $gsettingValue = [uint32]$matches[3]
392 |
393 | } elseif ($gsetting -match "^(.*?) (.*?) (.*)$") {
394 | # Using ? in first 2 groups to get lazy regex
395 | # so it would match lines ending with (includes additional space): @a{ss} {}
396 | # org.gnome.desktop.search-providers disabled $as []
397 |
398 | # There was no uint32 type value
399 |
400 | # setting is boolean, integer or string type (we ignore other variable types)
401 | $gsettingName = "gsettings get $($matches[1]) $($matches[2])"
402 | $Value = $matches[3]
403 |
404 | if(($Value -is [int32]) -or ($Value -is [uint32]) -or ($Value -is [int64])) {
405 |
406 | # $Value is integer
407 | $gsettingValue = [int64]$Value
408 |
409 | } elseif(($Value -eq "true") -or ($Value -eq "false")) {
410 |
411 | # value is boolean
412 | if($Value -eq "true") {
413 | $gsettingValue = $True
414 | } else {
415 | $gsettingValue = $False
416 | }
417 |
418 | } else {
419 | # Save Value as string
420 | $gsettingValue = [string]$Value
421 | }
422 |
423 | } else {
424 | # Regex did not match anything
425 | $logData += "regex did not catch this: $gsetting"
426 |
427 | }
428 |
429 | # Add gsetting to hash if we have Hash Key-value -> our regex worked
430 | if($gsettingName) {
431 | if(-not ($gsettingsHash.ContainsKey($gsettingName))) {
432 | $gsettingsHash.Add($gsettingName, $gsettingValue)
433 | } else {
434 | $logData += "Skipped duplicate hash Key: $gsettingName"
435 | }
436 | } else {
437 | $logData += "Regex did not catch gsettings line. Fix regex in this script! $gsettingName"
438 | }
439 | }
440 |
441 | return $gsettingsHash
442 | }
443 | ### function end ###
444 |
445 |
446 | # Get all gsettings to array
447 | $logData += "Get all gsettings"
448 | $gsettingsArray = & /bin/gsettings list-recursively
449 |
450 | # Parse gsettingsArray results and return hashtable
451 | $logData += "Parse gsettingsArray results and return hashtable"
452 | $gsettingsHashtable = Add_gsettingsArrayToHash $gsettingsArray
453 |
454 |
455 | # $SelectedGsettingsArray this array was manually specified in the beginning of this block
456 | # Add specified gsettings to $hash
457 | foreach($gsetting in $SelectedGsettingsArray) {
458 | # Make sure hash Key exists in $gsettingsHashtable before adding it to $hash
459 | if($gsettingsHashtable.ContainsKey($gsetting)) {
460 | if(-not ($hash.ContainsKey($gsetting))) {
461 | $logData += "Add to `$hash: Key: $gsetting Value: $($gsettingsHashtable[$gsetting])"
462 | $hash.Add($gsetting, $gsettingsHashtable[$gsetting])
463 | } else {
464 | $logData += "Skipped duplicate hash Name: $gsetting"
465 | $logData += "This should not be possible case normally"
466 | }
467 | } else {
468 | $logData += "`$gsettingsHashtable Key not found: $gsetting"
469 | $logData += "usually this means there is typo in `$SelectedGsettingsArray gsettings entries"
470 | }
471 | }
472 |
473 |
474 | <#
475 | # Do not do below because JSON will get BIG.
476 | # Left code here just in case needed in the future
477 | #
478 | # Add all $gsettingsHashtable hash values to $hash which we will convert to JSON in the end
479 | foreach($key in $gsettingsHashtable.keys) {
480 | if(-not ($hash.ContainsKey($key))) {
481 | $hash.Add($key, $gsettingsHashtable[$key])
482 | } else {
483 | $logData += "Skipped duplicate hash Key: $($key)"
484 | $logData += "This should not be possible case normally"
485 | }
486 | }
487 | #>
488 |
489 |
490 | ####################################################################################
491 | # Check Microsoft Defender Endpoint for Linux status
492 |
493 | $logData += "###################################################"
494 | $logData += "Check Microsoft Defender Endpoint for Linux status`n"
495 |
496 | # Check mdatp is installed in default path
497 | $mdatpPath = "/opt/microsoft/mdatp/sbin/wdavdaemonclient"
498 | if(Test-Path $mdatpPath) {
499 | $MicrosoftDefenderForEndpointOnLinux_Installed = $True
500 |
501 | # Check Defender is registered to organization
502 | $mdatp_org_id = & $mdatpPath --field org_id
503 |
504 | # Example value includes also double quotes
505 | # "0899fda5-fcad-42ef-9325-f8a0c7e30560"
506 | $regex = "^?[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}?$"
507 | if($mdatp_org_id -match $regex) {
508 | # Found valid guid
509 | $MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization = $True
510 | } else {
511 | # Did not find valid guid
512 | $MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization = $True
513 | }
514 |
515 | # Check Defender is functioning properly (is healthy)
516 | $mdatp_healthy = & $mdatpPath health --field healthy
517 |
518 | if($mdatp_healthy -eq "true") {
519 | # Defender is healthy
520 | $MicrosoftDefenderForEndpointOnLinux_Healthy = $True
521 | } else {
522 | # Defender is NOT healthy
523 | $MicrosoftDefenderForEndpointOnLinux_Healthy = $False
524 | }
525 |
526 | # Check Defender definitions are up_to_date
527 | $mdatp_definitions_status = & $mdatpPath health --field definitions_status
528 |
529 | if($mdatp_definitions_status -eq "`"up_to_date`"") {
530 | # Definitions are up to date
531 | $MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date = $True
532 | } else {
533 | # Definitions are NOT up to date
534 | $MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date = $False
535 | }
536 |
537 | # Check Defender real_time_protection_enabled
538 | $mdatp_real_time_protection_enabled = & $mdatpPath health --field real_time_protection_enabled
539 |
540 | if($mdatp_real_time_protection_enabled -eq "true") {
541 | # Defender Realtime Protection is enabled
542 | $MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled = $True
543 | } else {
544 | # Defender Realtime Protection is NOT enabled
545 | $MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled = $False
546 | }
547 |
548 | } else {
549 | # Microsoft Defender Endpoint for Linux status is NOT installed
550 |
551 | $MicrosoftDefenderForEndpointOnLinux_Installed = $False
552 | $MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization = $False
553 | $MicrosoftDefenderForEndpointOnLinux_Healthy = $False
554 | $MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date = $False
555 | $MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled = $False
556 |
557 | }
558 |
559 | # Add Defender information to $hash
560 |
561 | $logData += "MicrosoftDefenderForEndpointOnLinux_Installed=$MicrosoftDefenderForEndpointOnLinux_Installed"
562 | $hash.Add("MicrosoftDefenderForEndpointOnLinux_Installed", $MicrosoftDefenderForEndpointOnLinux_Installed)
563 |
564 | $logData += "MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization=$MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization"
565 | $hash.Add("MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization", $MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization)
566 |
567 | $logData += "MicrosoftDefenderForEndpointOnLinux_Healthy=$MicrosoftDefenderForEndpointOnLinux_Healthy"
568 | $hash.Add("MicrosoftDefenderForEndpointOnLinux_Healthy", $MicrosoftDefenderForEndpointOnLinux_Healthy)
569 |
570 | $logData += "MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date=$MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date"
571 | $hash.Add("MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date", $MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date)
572 |
573 | $logData += "MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled=$MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled"
574 | $hash.Add("MicrosoftDefenderForEndpointOnLinux_Real_time_protection_enabled", $MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled)
575 |
576 |
577 | $logData += "###################################################"
578 |
579 | # Convert hashtable to JSON
580 | $returnJson = $hash | ConvertTo-Json -Compress
581 |
582 | $logData += "Custom Compliance returnJson:`n"
583 | $logData += "$returnJson"
584 | $logData += ""
585 |
586 | $logData += "###################################################"
587 | $logData += "Powershell script end`n"
588 |
589 | # Calculate script runtime
590 | $ScriptEndTime = Get-Date
591 | $ScriptRunTime = New-Timespan $ScriptStartTime $ScriptEndTime
592 | $logData += "Script runtime $($ScriptRunTime.Minutes) min $($ScriptRunTime.Seconds) sec $($ScriptRunTime.Milliseconds) millisec"
593 |
594 | $logData += "###################################################"
595 | $logData += "Possible error data below`n"
596 |
597 | # Export log array to log file
598 | $logData | Out-File -FilePath /tmp/IntuneCustomComplianceScript_Powershell.log -Force
599 |
600 | # Export error data to help debugging errors in Powershell
601 | # Of course we will not make any errors but just in case :)
602 | if($Error) {
603 | $Error | Out-File -FilePath /tmp/IntuneCustomComplianceScript_Powershell.log -Append
604 | }
605 |
606 | # Return compressed JSON
607 | return $returnJson
608 | '
609 |
610 | echo "####################################################" >> $BashLogFilePath
611 | echo "Running Powershell script: $PowerShellCommandsString" >> $BashLogFilePath
612 | echo "####################################################" >> $BashLogFilePath
613 |
614 | # Check that pwsh exist
615 | PWSHPATH=/opt/microsoft/powershell/7/pwsh
616 | if [ -f "$PWSHPATH" ]; then
617 | # Powershell found
618 | # Run Powershell Commands
619 |
620 | echo "Run Powershell script part" >> $BashLogFilePath
621 | JSON=$($PWSHPATH -Command "& { ${PowerShellCommandsString} }")
622 | ExitCode=$?
623 | echo "Powershell script run exitcode: $ExitCode" >> $BashLogFilePath
624 | echo "JSON From Powershell" >> $BashLogFilePath
625 | echo "" >> $BashLogFilePath
626 | echo "$JSON" >> $BashLogFilePath
627 | echo "" >> $BashLogFilePath
628 | echo "Bash script end" >> $BashLogFilePath
629 | echo "$JSON"
630 | else
631 | # pwsh (Powershell) not found
632 | # return hardcoded json which will make device Not Compliant
633 |
634 | echo "pwsh not found from path: $PWSHPATH" >> $BashLogFilePath
635 | echo "Compliance Check will fail" >> $BashLogFilePath
636 | JSON='{"PowershellInstalled":false}'
637 | echo "Return json:" >> $BashLogFilePath
638 | echo "$JSON" >> $BashLogFilePath
639 | echo "Bash script end" >> $BashLogFilePath
640 | echo "$JSON"
641 | fi
642 |
--------------------------------------------------------------------------------
/Linux/Intune_Linux_Custom_Compliance_script_Rules_file.json:
--------------------------------------------------------------------------------
1 | {
2 | "Rules":[
3 | {
4 | "SettingName":"PowershellInstalled",
5 | "Operator":"IsEquals",
6 | "DataType":"Boolean",
7 | "Operand":true,
8 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.2",
9 | "RemediationStrings":[
10 | {
11 | "Language": "en_US",
12 | "Title": "Powershell not installed",
13 | "Description": "Powershell is required with Compliance Checks scripts. Install Powershell and Refresh settings."
14 | }
15 | ]
16 | },
17 | {
18 | "SettingName":"PSVersion",
19 | "Operator":"GreaterEquals",
20 | "DataType":"Version",
21 | "Operand":"7.3.3",
22 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.2",
23 | "RemediationStrings":[
24 | {
25 | "Language": "en_US",
26 | "Title": "Powershell version is not up to date.",
27 | "Description": "Required Powershell version 7.3.3 provides tools used with Compliance Checks scripts. Update Powershell and Refresh settings."
28 | }
29 | ]
30 | },
31 | {
32 | "SettingName":"SecureBootEnabled",
33 | "Operator":"IsEquals",
34 | "DataType":"Boolean",
35 | "Operand":true,
36 | "MoreInfoUrl":"https://wiki.ubuntu.com/UEFI/SecureBoot",
37 | "RemediationStrings":[
38 | {
39 | "Language": "en_US",
40 | "Title": "SecureBoot is not enabled.",
41 | "Description": "SecureBoot needs to enabled to get access to company resources. Enable SecureBoot and Refresh settings."
42 | }
43 | ]
44 | },
45 | {
46 | "SettingName":"MicrosoftDefenderForEndpointOnLinux_Installed",
47 | "Operator":"IsEquals",
48 | "DataType":"Boolean",
49 | "Operand":true,
50 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-endpoint-linux",
51 | "RemediationStrings":[
52 | {
53 | "Language": "en_US",
54 | "Title": "Your Organization requires Microsoft Defender for Endpoint on Linux devices.",
55 | "Description": "Install Defender for Endpoint and try again."
56 | }
57 | ]
58 | },
59 | {
60 | "SettingName":"MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization",
61 | "Operator":"IsEquals",
62 | "DataType":"Boolean",
63 | "Operand":true,
64 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-endpoint-linux",
65 | "RemediationStrings":[
66 | {
67 | "Language": "en_US",
68 | "Title": "Your Organization requires Microsoft Defender for Endpoint on Linux devices is registered to your organization.",
69 | "Description": "Configure Defender for Endpoint and try again."
70 | }
71 | ]
72 | },
73 | {
74 | "SettingName":"MicrosoftDefenderForEndpointOnLinux_Healthy",
75 | "Operator":"IsEquals",
76 | "DataType":"Boolean",
77 | "Operand":true,
78 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-endpoint-linux",
79 | "RemediationStrings":[
80 | {
81 | "Language": "en_US",
82 | "Title": "Your Organization requires Microsoft Defender for Endpoint on Linux devices is in healthy state.",
83 | "Description": "Configure and/or update Defender for Endpoint and try again."
84 | }
85 | ]
86 | },
87 | {
88 | "SettingName":"MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date",
89 | "Operator":"IsEquals",
90 | "DataType":"Boolean",
91 | "Operand":true,
92 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-endpoint-linux",
93 | "RemediationStrings":[
94 | {
95 | "Language": "en_US",
96 | "Title": "Your Organization requires Microsoft Defender for Endpoint on Linux devices is updated.",
97 | "Description": "Update Defender for Endpoint Definitions and try again."
98 | }
99 | ]
100 | },
101 | {
102 | "SettingName":"MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled",
103 | "Operator":"IsEquals",
104 | "DataType":"Boolean",
105 | "Operand":true,
106 | "MoreInfoUrl":"https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-endpoint-linux",
107 | "RemediationStrings":[
108 | {
109 | "Language": "en_US",
110 | "Title": "Your Organization requires Microsoft Defender for Endpoint on Linux devices has Realtime scan enable.",
111 | "Description": "Configure Defender for Endpoint Realtime settings and try again."
112 | }
113 | ]
114 | },
115 | {
116 | "SettingName":"RebootRequired",
117 | "Operator":"IsEquals",
118 | "DataType":"Boolean",
119 | "Operand":false,
120 | "MoreInfoUrl":"https://www.google.com",
121 | "RemediationStrings":[
122 | {
123 | "Language": "en_US",
124 | "Title": "Reboot Required detected.",
125 | "Description": "Reboot computer and Refresh settings."
126 | }
127 | ]
128 | },
129 | {
130 | "SettingName":"MSEdgeInstalled",
131 | "Operator":"IsEquals",
132 | "DataType":"Boolean",
133 | "Operand":true,
134 | "MoreInfoUrl":"https://www.microsoft.com/en-us/edge",
135 | "RemediationStrings":[
136 | {
137 | "Language": "en_US",
138 | "Title": "Microsoft Edge browser is not installed.",
139 | "Description": "Microsoft Edge browser is used for safe access to company resources. Install Microsoft Edge browser and Refresh settings."
140 | }
141 | ]
142 | },
143 | {
144 | "SettingName":"MSEdgeVersion",
145 | "Operator":"GreaterEquals",
146 | "DataType":"Version",
147 | "Operand":"111.0.1661.41",
148 | "MoreInfoUrl":"https://www.microsoft.com/en-us/edge",
149 | "RemediationStrings":[
150 | {
151 | "Language": "en_US",
152 | "Title": "Microsoft Edge version is not up to date.",
153 | "Description": "Required Microsfot Edge version should be at least 111.0.1661.41. Update Microsoft Edge and Refresh settings."
154 | }
155 | ]
156 | },
157 | {
158 | "SettingName":"KernelVersion",
159 | "Operator":"GreaterEquals",
160 | "DataType":"Version",
161 | "Operand":"5.19.0",
162 | "MoreInfoUrl":"https://www.google.com",
163 | "RemediationStrings":[
164 | {
165 | "Language": "en_US",
166 | "Title": "Linux kernel version is not up to date.",
167 | "Description": "Required kernel version should be at least 5.19.0. Update your device and Refresh settings."
168 | }
169 | ]
170 | },
171 | {
172 | "SettingName":"KernelPatchLevel",
173 | "Operator":"GreaterEquals",
174 | "DataType":"Version",
175 | "Operand":"35.0",
176 | "MoreInfoUrl":"https://www.google.com",
177 | "RemediationStrings":[
178 | {
179 | "Language": "en_US",
180 | "Title": "Linux kernel Patch version is not up to date.",
181 | "Description": "Required kernel Patch version should be at least 35. Update your device and Refresh settings."
182 | }
183 | ]
184 | },
185 | {
186 | "SettingName":"KernelFlavour",
187 | "Operator":"IsEquals",
188 | "DataType":"String",
189 | "Operand":"generic",
190 | "MoreInfoUrl":"https://www.microsoft.com/en-us/edge",
191 | "RemediationStrings":[
192 | {
193 | "Language": "en_US",
194 | "Title": "Linux kernel flavour is wrong.",
195 | "Description": "Kernel flavour should be generic. Change kernel to generic flavour and Refresh settings."
196 | }
197 | ]
198 | },
199 | {
200 | "SettingName":"TaintedKernelState",
201 | "Operator":"IsEquals",
202 | "DataType":"Int64",
203 | "Operand":0,
204 | "MoreInfoUrl":"https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html#decoding-tainted-state-at-runtime",
205 | "RemediationStrings":[
206 | {
207 | "Language": "en_US",
208 | "Title": "Linux kernel is in tainted mode.",
209 | "Description": "Tainted kernel can cause a lot of problems. NVIDIA or AMD graphics drivers can cause kernel tainted mode. Contact your administrator if you think this is false positive error. Reboot computer and Refresh settings."
210 | }
211 | ]
212 | },
213 | {
214 | "SettingName":"gsettings get org.gnome.desktop.screensaver lock-enabled",
215 | "Operator":"IsEquals",
216 | "DataType":"Boolean",
217 | "Operand":true,
218 | "MoreInfoUrl":"https://help.ubuntu.com/stable/ubuntu-help/privacy-screen-lock.html.en",
219 | "RemediationStrings":[
220 | {
221 | "Language": "en_US",
222 | "Title": "Automatic screen lock is not enabled.",
223 | "Description": "Screen should lock automatically when screensaver is enabled. Change screen lock setting and Refresh settings."
224 | }
225 | ]
226 | },
227 | {
228 | "SettingName":"gsettings get org.gnome.desktop.screensaver idle-activation-enabled",
229 | "Operator":"IsEquals",
230 | "DataType":"Boolean",
231 | "Operand":true,
232 | "MoreInfoUrl":"https://help.ubuntu.com/stable/ubuntu-help/privacy-screen-lock.html.en",
233 | "RemediationStrings":[
234 | {
235 | "Language": "en_US",
236 | "Title": "Automatic Screensaver idle activation not enabled.",
237 | "Description": "Enable idle activation lock with command: gsettings set org.gnome.desktop.screensaver idle-activation-enabled true and Refresh settings."
238 | }
239 | ]
240 | },
241 | {
242 | "SettingName":"gsettings get org.gnome.desktop.session idle-delay",
243 | "Operator":"LessEquals",
244 | "DataType":"Int64",
245 | "Operand":600,
246 | "MoreInfoUrl":"https://help.ubuntu.com/stable/ubuntu-help/display-blank.html.en",
247 | "RemediationStrings":[
248 | {
249 | "Language": "en_US",
250 | "Title": "Screen Blank timeout over 10 minutes.",
251 | "Description": "Screen Blank maximum timeout is 10 minutes. Change Screen Blank timeout setting and Refresh settings."
252 | }
253 | ]
254 | }
255 | ]
256 | }
--------------------------------------------------------------------------------
/Linux/README.md:
--------------------------------------------------------------------------------
1 | # Intune Linux Custom Compliance script 3.0
2 | Example scripts how to run Linux Custom Compliance checks with Intune.
3 | This script is Bash script which includes Powershell script inside.
4 |
5 | 
6 | 
7 |
8 | ## Quick links to files:
9 | * [Intune_Linux_Custom_Compliance_script.sh](https://github.com/petripaavola/Intune/blob/master/Linux/Intune_Linux_Custom_Compliance_script.sh)
10 | * [Intune_Linux_Custom_Compliance_script_Rules_file.json](https://github.com/petripaavola/Intune/blob/master/Linux/Intune_Linux_Custom_Compliance_script_Rules_file.json)
11 |
12 | ## Custom Compliance checks
13 | Custom Compliance checks are configured in **.json**-file ([Intune_Linux_Custom_Compliance_script_Rules_file.json](https://github.com/petripaavola/Intune/blob/master/Linux/Intune_Linux_Custom_Compliance_script_Rules_file.json))
14 |
15 | You can configure, enable and disable custom compliance check settings in json file without even touching to .sh script. For correct json and rule operator syntax it is best to copy existing rule and edit values on that.
16 |
17 | **Note! Json rule name is case-sensitive so make sure you have exactly same value in json that script returns!**
18 |
19 | You can validate json syntax with command (red means json is not valid)
20 | ```
21 | Get-Content -Path .\Intune_Linux_Custom_Compliance_script_Rules_file.json | ConvertFrom-Json
22 | ```
23 | ## Custom Compliance checks in this version:
24 | * **Powershell is installed**
25 | * **Powershell version**
26 | * 7.3.3 minimum currently configured in json
27 | * **Reboot Required check**
28 | * file should not exist /var/run/reboot-required)
29 | * **MS Edge**
30 | * Check MS Edge is installed
31 | * file should exist /opt/microsoft/msedge/msedge
32 | * Check MS Edge version
33 | * **Kernel version**
34 | * Check Kernel version
35 | * 5.19.35-generic minimum currently configured in json
36 | * Check Kernel patch level
37 | * Check Kernel flavour
38 | * Check Kernel tainted state
39 | * **SecureBoot status**
40 | * require SecureBoot configured in json
41 | * **sysctrl values** (placeholder to check any value)
42 | * user.max_user_namespaces
43 | * **gsettings values** (placeholder to check any value)
44 | * org.gnome.desktop.screensaver lock-enabled
45 | * org.gnome.desktop.screensaver idle-activation-enabled
46 | * org.gnome.desktop.session idle-delay
47 | * 10 minutes currently configured in json
48 | * **Defender for Endpoint on Linux status**
49 | * MicrosoftDefenderForEndpointOnLinux_Installed
50 | * MicrosoftDefenderForEndpointOnLinux_RegisteredToOrganization
51 | * MicrosoftDefenderForEndpointOnLinux_Healthy
52 | * MicrosoftDefenderForEndpointOnLinux_DefinitionsStatus_up_to_date
53 | * MicrosoftDefenderForEndpointOnLinux_real_time_protection_enabled
54 | ## How script works
55 | Linux Compliance script itself is Bash script because POSIX-compliant shell script is requirement for Intune. However script includes Powershell script and compliance checks are done in Powershell part.
56 |
57 | So Bash script is just launcher for Powershell script
58 |
59 | Script creates 2 **log files** for debugging and testing
60 | * **/tmp/IntuneCustomComplianceScript_Bash.log**
61 | * **/tmp/IntuneCustomComplianceScript_Powershell.log**
62 |
63 | You can also check syslog when Refreshing settings
64 | ```
65 | tail -f /var/log/syslog
66 | ```
67 |
68 | Linux custom compliance script runs in user context who enrolled Ubuntu device to Intune. You can verify this from log files.
69 |
70 | When you upload scripts to Intune they are usually immediately used in client on next Refresh so you can edit code and test really quickly. You can verify this from log files -> change script version number for example.
71 |
72 | Be sure NOT to write anything to STDOUT because that will break the compliance check. Only Powershell part of script is allowed to return compressed JSON to STDOUT which is then passed to Intune for custom Compliance Check.
73 |
74 | ## Requirements
75 | * **Powershell must be installed for this script to work**
76 | * Script will check existence of Powershell (/opt/microsoft/powershell/7/pwsh)
77 | * Check these [Microsoft instructions](https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.2) how to install Powershell to Ubuntu
78 | * You can create Dynamic Azure AD Group for targeting the Linux Compliance Policy using this rule
79 | * **(device.deviceOSType -eq "Linux")**
80 |
81 | ## Limits
82 | * Check current limits from [Microsoft documentation](https://learn.microsoft.com/en-us/mem/intune/protect/compliance-custom-script#limits)
83 | * Scripts can be no larger than 1 megabyte (MB) each.
84 | * Output generated by each script can be no larger than 1 MB.
85 | * Scripts must have a limited run time:
86 | * **On Linux, scripts must take five minutes or less to run.**
87 | * Note! You can find script total runtime from end of log file /tmp/IntuneCustomComplianceScript_Powershell.log
88 | * On Windows, scripts must take 10 minutes or less to run.
89 | * Own personal note: script runs in user context who enrolled device to Intune. This might limit being creative and thinking outside the box :)
90 |
91 | ## Add script to Microsoft Intune
92 | In [Microsoft Intune](https://intune.microsoft.com) add Linux Custom Compliance script
93 | **Devices -> Compliance policies -> Script -> Add**
94 |
95 |
96 | Then create Linux Compliance Policy and select Custom Compliance from Settings picker
97 | **Devices -> Linux -> Compliance policies -> Create policy**
98 |
99 |
100 |
101 |
102 | ## Screenshots
103 | 
104 | 
105 |
106 | **Intune Linux Compliance Policy**
107 |
108 | 
109 |
--------------------------------------------------------------------------------
/Linux/pics/IntuneAddCustomCompliancePolicySelectFiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/IntuneAddCustomCompliancePolicySelectFiles.png
--------------------------------------------------------------------------------
/Linux/pics/IntuneAddCustomCompliancePolicySettingsPicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/IntuneAddCustomCompliancePolicySettingsPicker.png
--------------------------------------------------------------------------------
/Linux/pics/IntuneAddLinuxCustomComplianceScript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/IntuneAddLinuxCustomComplianceScript.png
--------------------------------------------------------------------------------
/Linux/pics/Intune_Linux_Compliance_Policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/Intune_Linux_Compliance_Policy.png
--------------------------------------------------------------------------------
/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant.png
--------------------------------------------------------------------------------
/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_Compliant_small.png
--------------------------------------------------------------------------------
/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant.png
--------------------------------------------------------------------------------
/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petripaavola/Intune/c8b187f96e7e0462b91625e8b81c756817927dd6/Linux/pics/Intune_Linux_CustomComplianceCheck_RebootRequired_NotCompliant_small.png
--------------------------------------------------------------------------------
/Microsoft_Graph_API/AppRegistrations_Secret_Expiry_Report_v1.ps1:
--------------------------------------------------------------------------------
1 | # App registration secret expiration report console version
2 | # NVS_2022_Application
3 | # 17.3.2022
4 | #
5 | # Petri.Paavola@yodamiitti.fi
6 | # Microsoft MVP - Windows and Devices for IT
7 | # https://www.github.com/petripaavola
8 |
9 | #############################################################
10 | #region App information
11 | $AppId = 'f779d5d4-170b-FIXME'
12 | $TenantId = 'bef185b9-FIXME'
13 | $Scope = 'https://graph.microsoft.com/.default'
14 | # $AppSecret = 'REMOVED'
15 |
16 | # Export AppSecret to local disk with Export-CliXML
17 | # This encrypts secret and works only on that specific computer
18 | # $Cred = Get-Credential
19 | # $Cred
20 | # $Cred | Export-CliXml -Path .\AppSecret.xml
21 |
22 | # Import App Registration Secret from XML file
23 | Try {
24 | $Cred = Import-Clixml -Path "$PSScriptRoot\AppSecret.xml"
25 | $Success = $?
26 | if(-not $Success) {
27 | Write-Error "Error importing Application Secret!"
28 | Exit 1
29 | }
30 |
31 | # Get App secret in clear text
32 | $AppSecret = (New-Object PSCredential "user",$Cred.Password).GetNetworkCredential().Password
33 | #Write-Host "`$AppSecret=$AppSecret"
34 |
35 | } Catch {
36 | Write-Error "$($_.Exception.Message)"
37 | Write-Error "Error importing Application Secret!"
38 | Exit 2
39 | }
40 | #endregion
41 |
42 | #############################################################
43 | #region Do Azure AD Application based Authentication to Graph API
44 |
45 | $Url = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
46 |
47 | # Add System.Web for urlencode
48 | Add-Type -AssemblyName System.Web
49 |
50 | # Create body
51 | $Body = @{
52 | client_id = $AppId
53 | client_secret = $AppSecret
54 | scope = $Scope
55 | grant_type = 'client_credentials'
56 | }
57 |
58 | # Splat the parameters for Invoke-RestMethod for cleaner code
59 | $PostSplat = @{
60 | ContentType = 'application/x-www-form-urlencoded'
61 | Method = 'POST'
62 | # Create string by joining bodylist with '&'
63 | Body = $Body
64 | Uri = $Url
65 | }
66 |
67 | Write-Host "Authenticate to Graph API using Azure AD Application based authentication"
68 |
69 | # Request the token!
70 | $Request = Invoke-RestMethod @PostSplat
71 | $Success = $?
72 |
73 | if($Success) {
74 | Write-Host "Success`n" -ForegroundColor 'Green'
75 | } else {
76 | Write-Host "Failed to authenticate using Azure AD Application secret" -ForegroundColor 'Red'
77 | Exit 3
78 | }
79 |
80 |
81 | # Create header
82 | $Header = @{
83 | Authorization = "$($Request.token_type) $($Request.access_token)"
84 | }
85 |
86 | #endregion
87 |
88 | #region Get date
89 | Write-Host "Get App Registrations"
90 | $url = "https://graph.microsoft.com/beta/myorganization/applications?$select=displayName,id,appId,info,createdDateTime,keyCredentials,passwordCredentials,deletedDateTime"
91 | $Request = Invoke-RestMethod -Uri $url -Headers $Header -Method Get -ContentType "application/json"
92 | $Success = $?
93 |
94 | # Check if Invoke-RestMethod returned True (which is success)
95 | if(-not $Success) {
96 | Write-Error "There was error getting Graph API data with url $url"
97 | Exit 0
98 | }
99 |
100 | # DEBUG
101 | #$Request
102 | #$Request | Format-List *
103 | #Exit 0
104 |
105 | # Note we are not handling possible Graph API .NextLink in this example !!!
106 | $AppRegistrations = $Request.Value
107 |
108 | #endregion
109 |
110 | #region Process date
111 |
112 | # DEBUG from script to clipboard
113 | # What data we have now compared to Graph Explorer ????????
114 | #$AppRegistrations | ConvertTo-Json -Depth 5 | Set-Clipboard
115 |
116 | Foreach($AppRegistration in $AppRegistrations) {
117 | $displayName = $AppRegistration.displayName
118 | $passwordCredentials = $AppRegistration.passwordCredentials
119 |
120 | if($passwordCredentials) {
121 | Write-Host "App Registration $displayName secrets expires" -ForegroundColor Cyan
122 | foreach($passwordCredential in $passwordCredentials) {
123 | $secretDisplayName = $passwordCredential.displayName
124 | $endDateTime = $passwordCredential.endDateTime
125 |
126 | $TimeSpan = New-TimeSpan (Get-Date) (Get-Date $endDateTime)
127 | $ExpiresInDays = $TimeSpan | Select-Object -ExpandProperty Days
128 |
129 | if($ExpiresInDays -gt 0) {
130 | # Secret is not expired yet
131 | Write-Host "$secretDisplayName password expires in $ExpiresInDays days" -ForegroundColor Green
132 | } else {
133 | # Secret has expired
134 | # Do something
135 | Write-Host "$secretDisplayName password expires in $ExpiresInDays days" -ForegroundColor Red
136 | }
137 | }
138 | Write-Host
139 | }
140 | }
141 |
142 | #endregion
143 |
144 |
145 | # DEBUG DATA
146 | #return $AppRegistrations
147 |
--------------------------------------------------------------------------------
/Microsoft_Graph_API/AppRegistrations_Secret_Expiry_Report_v2.ps1:
--------------------------------------------------------------------------------
1 | # App registration secret expiration report console and HTML
2 | # NVS_2022_Application
3 | # 17.3.2022
4 | #
5 | # Petri.Paavola@yodamiitti.fi
6 | # Microsoft MVP - Windows and Devices for IT
7 | # https://www.github.com/petripaavola
8 |
9 | #############################################################
10 | #region App information
11 | $AppId = 'f779d5d4-170b-FIXME'
12 | $TenantId = 'bef185b9-FIXME'
13 | $Scope = 'https://graph.microsoft.com/.default'
14 | # $AppSecret = 'REMOVED'
15 |
16 | # Export secret to local disk with Export-CliXML
17 | # This encrypts secret and works only on that specific computer
18 | # $Cred = Get-Credential
19 | # $Cred
20 | # $Cred | Export-CliXml -Path .\AppSecret.xml
21 |
22 | # Import App Registration Secret from XML file
23 | Try {
24 | $Cred = Import-Clixml -Path "$PSScriptRoot\AppSecret.xml"
25 | $Success = $?
26 | if(-not $Success) {
27 | Write-Error "Error importing Application Secret!"
28 | Exit 1
29 | }
30 |
31 | # Get App secret in clear text
32 | $AppSecret = (New-Object PSCredential "user",$Cred.Password).GetNetworkCredential().Password
33 | #Write-Host "`$AppSecret=$AppSecret"
34 |
35 | } Catch {
36 | Write-Error "$($_.Exception.Message)"
37 | Write-Error "Error importing Application Secret!"
38 | Exit 2
39 | }
40 | #endregion
41 |
42 | #############################################################
43 | #region Do Azure AD Application based Authentication to Graph API
44 |
45 | $Url = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
46 |
47 | # Add System.Web for urlencode
48 | Add-Type -AssemblyName System.Web
49 |
50 | # Create body
51 | $Body = @{
52 | client_id = $AppId
53 | client_secret = $AppSecret
54 | scope = $Scope
55 | grant_type = 'client_credentials'
56 | }
57 |
58 | # Splat the parameters for Invoke-RestMethod for cleaner code
59 | $PostSplat = @{
60 | ContentType = 'application/x-www-form-urlencoded'
61 | Method = 'POST'
62 | # Create string by joining bodylist with '&'
63 | Body = $Body
64 | Uri = $Url
65 | }
66 |
67 | Write-Host "Authenticate to Graph API using Azure AD Application based authentication"
68 |
69 | # Request the token!
70 | $Request = Invoke-RestMethod @PostSplat
71 | $Success = $?
72 |
73 | if($Success) {
74 | Write-Host "Success`n" -ForegroundColor 'Green'
75 | } else {
76 | Write-Host "Failed to authenticate using Azure AD Application secret" -ForegroundColor 'Red'
77 | Exit 3
78 | }
79 |
80 |
81 | # Create header
82 | $Header = @{
83 | Authorization = "$($Request.token_type) $($Request.access_token)"
84 | }
85 |
86 | #endregion
87 |
88 | #region Get date
89 | Write-Host "Get App Registrations"
90 | $url = "https://graph.microsoft.com/beta/myorganization/applications?$select=displayName,id,appId,info,createdDateTime,keyCredentials,passwordCredentials,deletedDateTime"
91 | $Request = Invoke-RestMethod -Uri $url -Headers $Header -Method Get -ContentType "application/json"
92 | $Success = $?
93 |
94 | # Check if Invoke-RestMethod returned True (which is success)
95 | if(-not $Success) {
96 | Write-Error "There was error getting Graph API data with url $url"
97 | Exit 0
98 | }
99 |
100 | # DEBUG
101 | #$Request
102 | #$Request | Format-List *
103 |
104 | # Note we are not handling possible Graph API .NextLink in this example !!!
105 | $AppRegistrations = $Request.Value
106 |
107 | #endregion
108 |
109 | #region Process date
110 |
111 | # DEBUG from script to clipboard
112 | # What data we have now compared to Graph Explorer ????????
113 | #$AppRegistrations | ConvertTo-Json -Depth 5 | Set-Clipboard
114 |
115 | # Create array for report
116 | $AppRegistrationSecretReportObjects = @()
117 |
118 | Foreach($AppRegistration in $AppRegistrations) {
119 | $displayName = $AppRegistration.displayName
120 | $passwordCredentials = $AppRegistration.passwordCredentials
121 |
122 | if($passwordCredentials) {
123 | $secretExpiresInformationString = $null
124 | Write-Host "App Registration $displayName secrets expires" -ForegroundColor Cyan
125 | foreach($passwordCredential in $passwordCredentials) {
126 | $secretDisplayName = $passwordCredential.displayName
127 | $endDateTime = $passwordCredential.endDateTime
128 |
129 | $TimeSpan = New-TimeSpan (Get-Date) (Get-Date $endDateTime)
130 | $ExpiresInDays = $TimeSpan | Select-Object -ExpandProperty Days
131 |
132 | if($ExpiresInDays -gt 0) {
133 | # Secret is not expired yet
134 | Write-Host "$secretDisplayName password expires in $ExpiresInDays days" -ForegroundColor Green
135 | $secretExpiresInformationString = $secretExpiresInformationString + "$secretDisplayName password expires in $ExpiresInDays days
"
136 | } else {
137 | # Secret has expired
138 | # Do something
139 | Write-Host "$secretDisplayName password expires in $ExpiresInDays days" -ForegroundColor Red
140 | $secretExpiresInformationString = $secretExpiresInformationString + "$secretDisplayName password expires in$ExpiresInDays days
"
141 | }
142 | }
143 | Write-Host
144 |
145 | # Create custom object
146 | $PowershellCustomObject = New-Object -TypeName psobject
147 | $PowershellCustomObject | Add-Member –Membertype NoteProperty –Name AppRegistrationDisplayName –Value $displayName
148 | $PowershellCustomObject | Add-Member –Membertype NoteProperty –Name SecretExpires –Value $secretExpiresInformationString
149 |
150 | # Add Custom object to array
151 | $AppRegistrationSecretReportObjects += $PowershellCustomObject
152 | }
153 | }
154 |
155 | #endregion
156 |
157 | #region Create HTML report
158 |
159 | # Create HTML Report
160 |
161 | $ReportHTML = $AppRegistrationSecretReportObjects | Select-Object -Property AppRegistrationDisplayName,SecretExpires | ConvertTo-Html -Fragment -PreContent "