From d7d2e66eb559c4230f1f19d06d7ca1716c4de385 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:06:05 -0400 Subject: [PATCH 001/107] Add files via upload Windows Auto Update check and remediation script for Intune. --- Check-NoAutoUpdate.ps1 | 26 ++++++++++++++++++++++++++ Remediate-NoAutoUpdate.ps1 | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Check-NoAutoUpdate.ps1 create mode 100644 Remediate-NoAutoUpdate.ps1 diff --git a/Check-NoAutoUpdate.ps1 b/Check-NoAutoUpdate.ps1 new file mode 100644 index 0000000..ba7249f --- /dev/null +++ b/Check-NoAutoUpdate.ps1 @@ -0,0 +1,26 @@ +# Define the registry path and value +$registryPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" +$valueName = "NoAutoUpdate" +$expectedValue = "1" + + +# Check if the registry path exists +if (Test-Path $registryPath) { + # Get the current value + $currentValue = Get-ItemPropertyValue -Path $registryPath -Name $valueName -ErrorAction SilentlyContinue + + # Check if the value is as expected + if ($currentValue -eq $expectedValue) { + # Return non-compliant status if value is not as expected + Write-Host "NonCompliant" + exit 1 + } else { + # Return compliant status if value is as expected + Write-Host "Compliant" + exit 0 + } +} else { + # Return non-compliant status if registry path doesn't exist + Write-Host "Compliant" + exit 0 +} diff --git a/Remediate-NoAutoUpdate.ps1 b/Remediate-NoAutoUpdate.ps1 new file mode 100644 index 0000000..ce6b903 --- /dev/null +++ b/Remediate-NoAutoUpdate.ps1 @@ -0,0 +1,18 @@ +# Define the registry path and value +$registryPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" +$valueName = "NoAutoUpdate" +$expectedValue = "0" + + +# Set the registry value +try { + Set-ItemProperty -Path $registryPath -Name $valueName -Value $correctValue + Write-Host "Registry value corrected." +} catch { + Write-Error "Error occurred while setting registry value." + exit 1 +} + +# Exit codes: +# 0 - Remediation successful +# 1 - Remediation failed From 2b686bb22432939950853a31f2f02f57ce5ff21d Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:11:47 -0400 Subject: [PATCH 002/107] Add files via upload DisableDualScan fix for Intune updates. --- Check-DisableDualScan.ps1 | 26 ++++++++++++++++++++++++++ Remediate-DisableDualScan.ps1 | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Check-DisableDualScan.ps1 create mode 100644 Remediate-DisableDualScan.ps1 diff --git a/Check-DisableDualScan.ps1 b/Check-DisableDualScan.ps1 new file mode 100644 index 0000000..975a2fe --- /dev/null +++ b/Check-DisableDualScan.ps1 @@ -0,0 +1,26 @@ +# Define the registry path and value +$registryPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate" +$valueName = "DisableDualScan" +$expectedValue = "0" + + +# Check if the registry path exists +if (Test-Path $registryPath) { + # Get the current value + $currentValue = Get-ItemPropertyValue -Path $registryPath -Name $valueName -ErrorAction SilentlyContinue + + # Check if the value is as expected + if ($currentValue -eq $expectedValue) { + # Return non-compliant status if value is not as expected + Write-Host "NonCompliant" + exit 1 + } else { + # Return compliant status if value is as expected + Write-Host "Compliant" + exit 0 + } +} else { + # Return non-compliant status if registry path doesn't exist + Write-Host "Compliant" + exit 0 +} diff --git a/Remediate-DisableDualScan.ps1 b/Remediate-DisableDualScan.ps1 new file mode 100644 index 0000000..78c843b --- /dev/null +++ b/Remediate-DisableDualScan.ps1 @@ -0,0 +1,18 @@ +# Define the registry path and value +$registryPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate" +$valueName = "DisableDualScan" +$expectedValue = "1" + + +# Set the registry value +try { + Set-ItemProperty -Path $registryPath -Name $valueName -Value $correctValue + Write-Host "Registry value corrected." +} catch { + Write-Error "Error occurred while setting registry value." + exit 1 +} + +# Exit codes: +# 0 - Remediation successful +# 1 - Remediation failed From 120ec662484497be2f49439f4011740b24fc5081 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 22 Mar 2024 00:09:17 -0400 Subject: [PATCH 003/107] Add files via upload Windows Deployment Automation based on updates to T-Minus script --- Intune-AutomationRunbookv1_0.ps1 | 3772 ++++++++++++++++++++++++++++++ 1 file changed, 3772 insertions(+) create mode 100644 Intune-AutomationRunbookv1_0.ps1 diff --git a/Intune-AutomationRunbookv1_0.ps1 b/Intune-AutomationRunbookv1_0.ps1 new file mode 100644 index 0000000..f95e515 --- /dev/null +++ b/Intune-AutomationRunbookv1_0.ps1 @@ -0,0 +1,3772 @@ +<# +.COPYRIGHT +Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +See LICENSE in the project root for license information. +#> + +<# +.SYNOPSIS +After you run this script, you will have + +1.White labeled Terms and Conditions +2.A device compliance policy for: Windows, iOS/ipad, Android for Work, Android Device Owner, macOS +3.A Windows 10 Endpoint Protection Profile for Windows Devices for Silent Bitlocker Encryption +4.365 Business Suite to Windows 10 and MacOS +5.Office Suite for iOS and Android (Word, PowerPoint, Excel, Teams, OneDrive, Outlook) +6.Microsoft Authenticator pushed out as a required App for iOS and Android devices +7.App Protection Policies for Android and iOS without enrollment +8.Windows Information Protection policies with or without enrollment (2 policies) + +#> + +#################################################### + +function Get-AuthToken { + +<# +.SYNOPSIS +This function is used to authenticate with the Graph API REST interface +.DESCRIPTION +The function authenticate with the Graph API Interface with the tenant name +.EXAMPLE +Get-AuthToken +Authenticates you with the Graph API interface +.NOTES +NAME: Get-AuthToken +#> + +[cmdletbinding()] + +param +( + [Parameter(Mandatory=$true)] + $User +) + +$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User + +$tenant = $userUpn.Host + +Write-Host "Checking for AzureAD module..." + + $AadModule = Get-Module -Name "AzureAD" -ListAvailable + + if ($AadModule -eq $null) { + + Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview" + $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable + + } + + if ($AadModule -eq $null) { + Write-Host "AzureAD PowerShell module not found, Installing Module" + $AadModule = Install-Module -Name "AzureAD" + } + + + + +# Getting path to ActiveDirectory Assemblies +# If the module count is greater than 1 find the latest version + + if($AadModule.count -gt 1){ + + $Latest_Version = ($AadModule | select version | Sort-Object)[-1] + + $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version } + + # Checking if there are multiple versions of the same module found + + if($AadModule.count -gt 1){ + + $aadModule = $AadModule | select -Unique + + } + + $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll" + $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll" + + } + + else { + + $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll" + $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll" + + } + +[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null + +[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null + +$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547" + +$redirectUri = "urn:ietf:wg:oauth:2.0:oob" + +$resourceAppIdURI = "https://graph.microsoft.com" + +$authority = "https://login.microsoftonline.com/$Tenant" + + try { + + $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority + + # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx + # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession + + $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto" + + $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId") + + $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result + + # If the accesstoken is valid then create the authentication header + + if($authResult.AccessToken){ + + # Creating header for Authorization token + + $authHeader = @{ + 'Content-Type'='application/json' + 'Authorization'="Bearer " + $authResult.AccessToken + 'ExpiresOn'=$authResult.ExpiresOn + } + + return $authHeader + + } + + else { + + Write-Host + Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red + Write-Host + break + + } + + } + + catch { + + write-host $_.Exception.Message -f Red + write-host $_.Exception.ItemName -f Red + write-host + break + + } + +} + +#################################################### + +Function Test-JSON(){ + +<# +.SYNOPSIS +This function is used to test if the JSON passed to a REST Post request is valid +.DESCRIPTION +The function tests if the JSON passed to the REST Post is valid +.EXAMPLE +Test-JSON -JSON $JSON +Test if the JSON is valid before calling the Graph REST interface +.NOTES +NAME: Test-JSON +#> + +param ( + +$JSON + +) + + try { + + $TestJSON = ConvertFrom-Json $JSON -ErrorAction Stop + $validJson = $true + + } + + catch { + + $validJson = $false + $_.Exception + + } + + if (!$validJson){ + + Write-Host "Provided JSON isn't in valid JSON format" -f Red + break + + } + +} + +#################################################### + +Function Get-itunesApplication(){ + +<# +.SYNOPSIS +This function is used to get an iOS application from the itunes store using the Apple REST API interface +.DESCRIPTION +The function connects to the Apple REST API Interface and returns applications from the itunes store +.EXAMPLE +Get-itunesApplication -SearchString "Microsoft Corporation" +Gets an iOS application from itunes store +.EXAMPLE +Get-itunesApplication -SearchString "Microsoft Corporation" -Limit 10 +Gets an iOS application from itunes store with a limit of 10 results +.NOTES +NAME: Get-itunesApplication +#> + +[cmdletbinding()] + +param +( + [Parameter(Mandatory=$true)] + $SearchString, + [int]$Limit +) + + try{ + + Write-Verbose $SearchString + + # Testing if string contains a space and replacing it with a + + $SearchString = $SearchString.replace(" ","+") + + Write-Verbose "SearchString variable converted if there is a space in the name $SearchString" + + if($Limit){ + + $iTunesUrl = "https://itunes.apple.com/search?country=us&media=software&entity=software,iPadSoftware&term=$SearchString&limit=$limit" + + } + + else { + + $iTunesUrl = "https://itunes.apple.com/search?entity=software&term=$SearchString&attribute=softwareDeveloper" + + } + + write-verbose $iTunesUrl + $apps = Invoke-RestMethod -Uri $iTunesUrl -Method Get + + # Putting sleep in so that no more than 20 API calls to itunes API + sleep 3 + + return $apps + + } + + catch { + + write-host $_.Exception.Message -f Red + write-host $_.Exception.ItemName -f Red + write-verbose $_.Exception + write-host + break + + } + +} + +#################################################### +Function Add-iOSApplication(){ + +<# +.SYNOPSIS +This function is used to add an iOS application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an iOS application from the itunes store +.EXAMPLE +Add-iOSApplication -AuthHeader $AuthHeader +Adds an iOS application into Intune from itunes store +.NOTES +NAME: Add-iOSApplication +#> + +[cmdletbinding()] + +param +( + $itunesApp +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps" + + try { + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + + $app = $itunesApp + + Write-Verbose $app + + Write-Host "Publishing $($app.trackName)" -f Yellow + + # Step 1 - Downloading the icon for the application + $iconUrl = $app.artworkUrl60 + + if ($iconUrl -eq $null){ + + Write-Host "60x60 icon not found, using 100x100 icon" + $iconUrl = $app.artworkUrl100 + + } + + if ($iconUrl -eq $null){ + + Write-Host "60x60 icon not found, using 512x512 icon" + $iconUrl = $app.artworkUrl512 + + } + + $iconResponse = Invoke-WebRequest $iconUrl + $base64icon = [System.Convert]::ToBase64String($iconResponse.Content) + $iconType = $iconResponse.Headers["Content-Type"] + + if(($app.minimumOsVersion.Split(".")).Count -gt 2){ + + $Split = $app.minimumOsVersion.Split(".") + + $MOV = $Split[0] + "." + $Split[1] + + $osVersion = [Convert]::ToDouble($MOV) + + } + + else { + + $osVersion = [Convert]::ToDouble($app.minimumOsVersion) + + } + + # Setting support Operating System Devices + if($app.supportedDevices -match "iPadMini"){ $iPad = $true } else { $iPad = $false } + if($app.supportedDevices -match "iPhone6"){ $iPhone = $true } else { $iPhone = $false } + + # Step 2 - Create the Hashtable Object of the application + + $description = $app.description -replace "[^\x00-\x7F]+","" + + $graphApp = @{ + "@odata.type"="#microsoft.graph.iosStoreApp"; + displayName=$app.trackName; + publisher=$app.artistName; + description=$description; + largeIcon= @{ + type=$iconType; + value=$base64icon; + }; + isFeatured=$false; + appStoreUrl=$app.trackViewUrl; + applicableDeviceType=@{ + iPad=$iPad; + iPhoneAndIPod=$iPhone; + }; + minimumSupportedOperatingSystem=@{ + v8_0=$osVersion -lt 9.0; + v9_0=$osVersion -eq 9.0; + v10_0=$osVersion -gt 9.0; + }; + }; + + $JSON = ConvertTo-Json $graphApp + + # Step 3 - Publish the application to Graph + Write-Host "Creating application via Graph" + $createResult = Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body (ConvertTo-Json $graphApp) -Headers $authToken + Write-Host "Application created as $uri/$($createResult.id)" + write-host + + return $createResult + + } + + catch { + + $ex = $_.Exception + Write-Host "Request to $Uri failed with HTTP Status $([int]$ex.Response.StatusCode) $($ex.Response.StatusDescription)" -f Red + + $errorResponse = $ex.Response.GetResponseStream() + + $ex.Response.GetResponseStream() + + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +################################################################### + +Function Add-TermsAndConditions(){ + +<# +.SYNOPSIS +This function is used to add Terms and Conditions using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds Terms and Conditions Statement +.EXAMPLE +Add-TermsAndConditions -JSON $JSON +Adds Terms and Conditions into Intune +.NOTES +NAME: Add-TermsAndConditions +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/termsAndConditions" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + Write-Host + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### +Function Add-TermsAndConditions(){ + +<# +.SYNOPSIS +This function is used to add Terms and Conditions using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds Terms and Conditions Statement +.EXAMPLE +Add-TermsAndConditions -JSON $JSON +Adds Terms and Conditions into Intune +.NOTES +NAME: Add-TermsAndConditions +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/termsAndConditions" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + Write-Host + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-TermsAndConditions(){ + +<# +.SYNOPSIS +This function is used to assign Terms and Conditions from Intune to a Group using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns terms and conditions to a group +.EXAMPLE +Assign-TermsAndConditions -id $id -TargetGroupId +.NOTES +NAME: Assign-TermsAndConditions +#> + +[cmdletbinding()] + +param +( + $id, + $TargetGroupId +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/termsAndConditions/$id/groupAssignments" + + try { + + if(!$id){ + + Write-Host "No Terms and Conditions ID was passed to the function, specify a valid terms and conditions ID" -ForegroundColor Red + Write-Host + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + Write-Host + break + + } + + else { + +$JSON = @" +{ + "targetGroupId":"$TargetGroupId" +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +########################################################################### + +Function Add-MDMApplication(){ + +<# +.SYNOPSIS +This function is used to add an MDM application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an MDM application from the itunes store +.EXAMPLE +Add-MDMApplication -JSON $JSON1 +Adds an application into Intune +.NOTES +NAME: Add-MDMApplication +#> + +[cmdletbinding()] + +param +( + $JSON1 +) + +$graphApiVersion = "Beta" +$App_resource = "deviceAppManagement/mobileApps" + + try { + + if(!$JSON1){ + + write-host "No JSON was passed to the function, provide a JSON variable" -f Red + break + + } + + Test-JSON -JSON $JSON1 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON1 -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON1 = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON1 -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +################################################################################### + +Function Add-DeviceCompliancePolicy(){ + +<# +.SYNOPSIS +This function is used to add a device compliance policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device compliance policy +.EXAMPLE +Add-DeviceCompliancePolicy -JSON $JSON +Adds an Android device compliance policy in Intune +.NOTES +NAME: Add-DeviceCompliancePolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "beta" +$Resource = "deviceManagement/deviceCompliancePolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + Write-Host + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-DeviceCompliancePolicyAssignment(){ + +<# +.SYNOPSIS +This function is used to add a device compliance policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device compliance policy assignment +.EXAMPLE +Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CompliancePolicyId -TargetGroupId $TargetGroupId +Adds a device compliance policy assignment in Intune +.NOTES +NAME: Add-DeviceCompliancePolicyAssignment +#> + +[cmdletbinding()] + +param +( + $CompliancePolicyId, + $TargetGroupId +) + +$graphApiVersion = "beta" +$Resource = "deviceManagement/deviceCompliancePolicies/$CompliancePolicyId/assign" + + try { + + if(!$CompliancePolicyId){ + + write-host "No Compliance Policy Id specified, specify a valid Compliance Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + +$JSON = @" + { + "assignments": [ + { + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + } + } + ] + } + +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + + +#################################################### + +Function Add-DeviceConfigurationPolicy(){ + +<# +.SYNOPSIS +This function is used to add an device configuration policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device configuration policy +.EXAMPLE +Add-DeviceConfigurationPolicy -JSON $JSON +Adds a device configuration policy in Intune +.NOTES +NAME: Add-DeviceConfigurationPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$DCP_resource = "deviceManagement/deviceConfigurations" +Write-Verbose "Resource: $DCP_resource" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### +Function Add-WindowsUpdatePolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows update (ring) policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an update ring configuration policy +.EXAMPLE +Add-WindowsUpdatePolicy -JSON $JSON +Adds a device update ring policy in Intune +.NOTES +NAME: Add-WindowsUpdatePolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "v1.0" +$DCP_resource = "/deviceManagement/deviceConfigurations" +Write-Verbose "Resource: $DCP_resource" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Windows Update Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### +Function Add-AndroidApplication(){ + +<# +.SYNOPSIS +This function is used to add an Android application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an Android application from the itunes store +.EXAMPLE +Add-AndroidApplication -JSON $JSON -IconURL pathtourl +Adds an Android application into Intune using an icon from a URL +.NOTES +NAME: Add-AndroidApplication +#> + +[cmdletbinding()] + +param +( + $JSON3, + $IconURL +) + +$graphApiVersion = "Beta" +$App_resource = "deviceAppManagement/mobileApps" + + try { + + if(!$JSON3){ + + write-host "No JSON was passed to the function, provide a JSON variable" -f Red + break + + } + + + if($IconURL){ + + write-verbose "Icon specified: $IconURL" + + if(!(test-path "$IconURL")){ + + write-host "Icon Path '$IconURL' doesn't exist..." -ForegroundColor Red + Write-Host "Please specify a valid path..." -ForegroundColor Red + Write-Host + break + + } + + $iconResponse = Invoke-WebRequest "$iconUrl" + $base64icon = [System.Convert]::ToBase64String($iconResponse.Content) + $iconExt = ([System.IO.Path]::GetExtension("$iconURL")).replace(".","") + $iconType = "image/$iconExt" + + Write-Verbose "Updating JSON to add Icon Data" + + $U_JSON3 = ConvertFrom-Json $JSON3 + + $U_JSON3.largeIcon.type = "$iconType" + $U_JSON3.largeIcon.value = "$base64icon" + + $JSON3 = ConvertTo-Json $U_JSON3 + + Write-Verbose $JSON3 + + Test-JSON -JSON $JSON3 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON3 -Headers $authToken + + } + + else { + + Test-JSON -JSON $JSON3 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON3 -Headers $authToken + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +######################################################### + +Function Add-DeviceConfigurationPolicyAssignment(){ + +<# +.SYNOPSIS +This function is used to add a device configuration policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device configuration policy assignment +.EXAMPLE +Add-DeviceConfigurationPolicyAssignment -ConfigurationPolicyId $ConfigurationPolicyId -TargetGroupId $TargetGroupId +Adds a device configuration policy assignment in Intune +.NOTES +NAME: Add-DeviceConfigurationPolicyAssignment +#> + +[cmdletbinding()] + +param +( + $ConfigurationPolicyId, + $TargetGroupId +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/deviceConfigurations/$ConfigurationPolicyId/assign" + + try { + + if(!$ConfigurationPolicyId){ + + write-host "No Configuration Policy Id specified, specify a valid Configuration Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + $ConfPolAssign = "$ConfigurationPolicyId" + "_" + "$TargetGroupId" + +$JSON = @" +{ + "deviceConfigurationGroupAssignments": [ + { + "@odata.type": "#microsoft.graph.deviceConfigurationGroupAssignment", + "id": "$ConfPolAssign", + "targetGroupId": "$TargetGroupId" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +######################################################### +<# +Function Add-WindowsUpdatePolicyAssignment(){ +#> +<# +.SYNOPSIS +This function is used to add a Windows Update configuration policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Update configuration policy assignment +.EXAMPLE +Add-WindowsUpdatePolicyAssignment -ConfigurationPolicyId $ConfigurationPolicyId -TargetGroupId $TargetGroupId +Adds a device Windows Update policy assignment in Intune +.NOTES +NAME: Add-WindowsUpdatePolicyAssignment +#> +<# +[cmdletbinding()] + +param +( + $ConfigurationPolicyId, + $TargetGroupId +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/deviceConfigurations/$ConfigurationPolicyId/assign" + + try { + + if(!$ConfigurationPolicyId){ + + write-host "No Configuration Policy Id specified, specify a valid Configuration Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + $ConfPolAssign = "$ConfigurationPolicyId" + "_" + "$TargetGroupId" + +$JSON = @" +{ + "deviceConfigurationGroupAssignments": [ + { + "@odata.type": "#microsoft.graph.deviceConfigurationGroupAssignment", + "id": "$ConfPolAssign", + "targetGroupId": "$TargetGroupId" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +#> +############################################################################# + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON4 = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON4 -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +########################################################################################## + +Function Add-ManagedAppPolicy(){ + +<# +.SYNOPSIS +This function is used to add an Managed App policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Managed App policy +.EXAMPLE +Add-ManagedAppPolicy -JSON $JSON +Adds a Managed App policy in Intune +.NOTES +NAME: Add-ManagedAppPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/managedAppPolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for a Managed App Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + Write-Host + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-ManagedAppPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a Managed App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a Managed App Policy with an AAD Group +.EXAMPLE +Assign-ManagedAppPolicy -Id $Id -TargetGroupId $TargetGroupId -OS Android +Assigns an AAD Group assignment to an Android App Protection Policy in Intune +.EXAMPLE +Assign-ManagedAppPolicy -Id $Id -TargetGroupId $TargetGroupId -OS iOS +Assigns an AAD Group assignment to an iOS App Protection Policy in Intune +.NOTES +NAME: Assign-ManagedAppPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId, + $OS +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + +$JSON = @" +{ + "assignments":[ + { + "target": + { + "groupId":"$TargetGroupId", + "@odata.type":"#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + if($OS -eq "" -or $OS -eq $null){ + + write-host "No OS parameter specified, please provide an OS. Supported value Android or iOS..." -f Red + Write-Host + break + + } + + elseif($OS -eq "Android"){ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/iosManagedAppProtections('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + elseif($OS -eq "iOS"){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/deviceAppManagement/iosManagedAppProtections('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +###################################################### + +Function Add-WindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows Information Protection policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Information Protection policy +.EXAMPLE +Add-WindowsInformationProtectionPolicy -JSON $JSON +Adds a Windows Information Protection policy in Intune +.NOTES +NAME: Add-WindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/windowsInformationProtectionPolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the iOS Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-MDMWindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows Information Protection policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Information Protection policy +.EXAMPLE +Add-MDMWindowsInformationProtectionPolicy -JSON $JSON +Adds a Windows Information Protection policy in Intune +.NOTES +NAME: Add-MDMWindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mdmWindowsInformationProtectionPolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the iOS Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-WindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a WIP App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a WIP App Policy with an AAD Group +.EXAMPLE +Assign-WindowsInformationProtectionPolicy -Id $Id -TargetGroupId $TargetGroupId +Assigns an AAD Group assignment to a WIP App Protection Policy in Intune +.NOTES +NAME: Assign-WindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + +$JSON = @" +{ + "assignments": [ + { + "target": { + "groupId": "$TargetGroupId", + "@odata.type": "#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/windowsInformationProtectionPolicies('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-MDMWindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a WIP App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a WIP App Policy with an AAD Group +.EXAMPLE +Assign-MDMWindowsInformationProtectionPolicy -Id $Id -TargetGroupId $TargetGroupId +Assigns an AAD Group assignment to a WIP App Protection Policy in Intune +.NOTES +NAME: Assign-MDMWindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + +$JSON = @" +{ + "assignments": [ + { + "target": { + "groupId": "$TargetGroupId", + "@odata.type": "#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/mdmWindowsInformationProtectionPolicies('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Get-EnterpriseDomain(){ + +<# +.SYNOPSIS +This function is used to get the initial domain created using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and gets the initial domain created +.EXAMPLE +Get-EnterpriseDomain +Gets the initial domain created in Azure - Format domain.onmicrosoft.com +.NOTES +NAME: Get-EnterpriseDomain +#> + + try { + + $uri = "https://graph.microsoft.com/v1.0/domains" + + $domains = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + $EnterpriseDomain = ($domains | ? { $_.isDefault -eq $true } | select id).id + + return $EnterpriseDomain + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + + } + +} +Function Get-TenantName(){ + +<# +.SYNOPSIS +This function is used to get the initial domain created using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and gets the initial domain created +.EXAMPLE +Get-EnterpriseDomain +Gets the initial domain created in Azure - Format domain.onmicrosoft.com +.NOTES +NAME: Get-EnterpriseDomain +#> + + try { + + $uri = "https://graph.microsoft.com/v1.0/organization" + + $orgData = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + $TenantName1 = $orgData.displayName + + return $TenantName1 + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + + } + +} + + + + +############################################ + +Function Get-AADGroup(){ + +<# +.SYNOPSIS +This function is used to get AAD Groups from the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and gets any Groups registered with AAD +.EXAMPLE +Get-AADGroup +Returns all users registered with Azure AD +.NOTES +NAME: Get-AADGroup +#> + +[cmdletbinding()] + +param +( + $GroupName, + $id, + [switch]$Members +) + +# Defining Variables +$graphApiVersion = "v1.0" +$Group_resource = "groups" + + try { + + if($id){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=id eq '$id'" + (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + } + + elseif($GroupName -eq "" -or $GroupName -eq $null){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)" + (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + } + + else { + + if(!$Members){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=displayname eq '$GroupName'" + (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + } + + elseif($Members){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)?`$filter=displayname eq '$GroupName'" + $Group = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + if($Group){ + + $GID = $Group.id + + $Group.displayName + write-host + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Group_resource)/$GID/Members" + (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value + + } + + } + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +#region Authentication + +write-host + +# Checking if authToken exists before running authentication +if($global:authToken){ + + # Setting DateTime to Universal time to work in all timezones + $DateTime = (Get-Date).ToUniversalTime() + + # If the authToken exists checking when it expires + $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes + + if($TokenExpires -le 0){ + + write-host "Authentication Token expired" $TokenExpires "minutes ago" -ForegroundColor Yellow + write-host + + # Defining User Principal Name if not present + + if($User -eq $null -or $User -eq ""){ + + $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication" + Write-Host + + } + + $global:authToken = Get-AuthToken -User $User + + } +} + +# Authentication doesn't exist, calling Get-AuthToken function + +else { + + if($User -eq $null -or $User -eq ""){ + + $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication" + Write-Host + + } + +# Getting the authorization token +$global:authToken = Get-AuthToken -User $User + +} + +#endregion + +#################################################### +<# Configurations using JSON #> +#################################################### + +$JSON_10QualityUpdate = @" +{ + "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", + "supportsScopeTags":true, + "roleScopeTagIds":["0"], + "deviceManagementApplicabilityRuleOsEdition":null, + "deviceManagementApplicabilityRuleOsVersion":null, + "deviceManagementApplicabilityRuleDeviceMode":null, + "description":"", + "displayName":"Windows 10 Update Ring", + "version":1, + "deliveryOptimizationMode":"userDefined", + "prereleaseFeatures":"userDefined", + "automaticUpdateMode":"autoInstallAndRebootAtMaintenanceTime", + "microsoftUpdateServiceAllowed":true, + "driversExcluded":false, + "qualityUpdatesDeferralPeriodInDays":0, + "featureUpdatesDeferralPeriodInDays":0, + "qualityUpdatesPaused":false, + "featureUpdatesPaused":false, + "qualityUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "businessReadyUpdatesOnly":"userDefined", + "skipChecksBeforeRestart":false, + "updateWeeks":null, + "qualityUpdatesPauseStartDate":null, + "featureUpdatesPauseStartDate":null, + "featureUpdatesRollbackWindowInDays":60, + "qualityUpdatesWillBeRolledBack":null, + "featureUpdatesWillBeRolledBack":null, + "qualityUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "engagedRestartDeadlineInDays":null, + "engagedRestartSnoozeScheduleInDays":null, + "engagedRestartTransitionScheduleInDays":null, + "deadlineForFeatureUpdatesInDays":15, + "deadlineForQualityUpdatesInDays":5, + "deadlineGracePeriodInDays":5, + "postponeRebootUntilAfterDeadline":false, + "autoRestartNotificationDismissal":"notConfigured", + "scheduleRestartWarningInHours":null, + "scheduleImminentRestartWarningInMinutes":null, + "userPauseAccess":"disabled", + "userWindowsUpdateScanAccess":"enabled", + "updateNotificationLevel":"restartWarningsOnly", + "allowWindows11Upgrade":false, + "installationSchedule":{ + "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", + "activeHoursStart":"07:00:00.0000000", + "activeHoursEnd":"19:00:00.0000000" + } +} +"@ + +#################################################### +$JSON_11QualityUpdate = @" +{ + "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", + "supportsScopeTags":true, + "roleScopeTagIds":["0"], + "deviceManagementApplicabilityRuleOsEdition":null, + "deviceManagementApplicabilityRuleOsVersion":null, + "deviceManagementApplicabilityRuleDeviceMode":null, + "description":"", + "displayName":"Windows 11 Update Ring", + "version":1, + "deliveryOptimizationMode":"userDefined", + "prereleaseFeatures":"userDefined", + "automaticUpdateMode":"autoInstallAndRebootAtMaintenanceTime", + "microsoftUpdateServiceAllowed":true, + "driversExcluded":false, + "qualityUpdatesDeferralPeriodInDays":0, + "featureUpdatesDeferralPeriodInDays":0, + "qualityUpdatesPaused":false, + "featureUpdatesPaused":false, + "qualityUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "businessReadyUpdatesOnly":"userDefined", + "skipChecksBeforeRestart":false, + "updateWeeks":null, + "qualityUpdatesPauseStartDate":null, + "featureUpdatesPauseStartDate":null, + "featureUpdatesRollbackWindowInDays":60, + "qualityUpdatesWillBeRolledBack":null, + "featureUpdatesWillBeRolledBack":null, + "qualityUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "engagedRestartDeadlineInDays":null, + "engagedRestartSnoozeScheduleInDays":null, + "engagedRestartTransitionScheduleInDays":null, + "deadlineForFeatureUpdatesInDays":15, + "deadlineForQualityUpdatesInDays":5, + "deadlineGracePeriodInDays":5, + "postponeRebootUntilAfterDeadline":false, + "autoRestartNotificationDismissal":"notConfigured", + "scheduleRestartWarningInHours":null, + "scheduleImminentRestartWarningInMinutes":null, + "userPauseAccess":"disabled", + "userWindowsUpdateScanAccess":"enabled", + "updateNotificationLevel":"restartWarningsOnly", + "allowWindows11Upgrade":true, + "installationSchedule":{ + "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", + "activeHoursStart":"07:00:00.0000000", + "activeHoursEnd":"19:00:00.0000000" + } +} +"@ + +#################################################### +$JSON_macOS = @" +{ + "@odata.type": "#microsoft.graph.macOSCompliancePolicy", + "description": "1. Require system integrity protection\n2. Minimum OS version: 10.13\n3. Require a password to unlock devices.\n4. Block Simple passwords\n5. Minimum password length: 8 Alphanumeric\n6. Number of non-alphanumeric characters in password: 1\n7. Maximum minutes of inactivity before password is required: 5 Minutes\n8. Password expiration (days): 90\n9: Number of previous passwords to prevent reuse: 5\n10. RequireEncryption of data storage on device.\n11. Enable Firewall\n12. Block Incoming connections\n13. Allow apps downloaded from these locations: Mac App Store", + "displayName": "macOS_Compliance", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "version": 2, + "passwordRequired": true, + "passwordBlockSimple": true, + "passwordExpirationDays": 90, + "passwordMinimumLength": 8, + "passwordMinutesOfInactivityBeforeLock": 5, + "passwordPreviousPasswordBlockCount": 5, + "passwordMinimumCharacterSetCount": 1, + "passwordRequiredType": "alphanumeric", + "osMinimumVersion": "10.13", + "osMaximumVersion": "Os Maximum Version value", + "systemIntegrityProtectionEnabled": true, + "deviceThreatProtectionEnabled": false, + "storageRequireEncryption": true, + "firewallEnabled": true, + "firewallBlockAllIncoming": true, + "firewallEnableStealthMode": false +} +"@ + +#################################################### + +$JSON_AndroidWork = @" + { + "@odata.type": "#microsoft.graph.androidWorkProfileCompliancePolicy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "description": "1. Block Rooted devices\n2. Google Play Services is configured\n3. SafetyNet device attestation: Check basic integrity\n4. Require a password to unlock mobile devices.\n5. Required password type: Numeric complex\n6. Maximum minutes of inactivity before password is required: 1 Minute\n7. Minimum password length: 6 \n8. Require Encryption of data storage on device.\n9. Block apps from unknown sources\n10. Require Company Portal app runtime integrity\n11.Block USB debugging on device\n12. Minimum security patch level: 2020-01-01", + "displayName": "Android_Work_Device", + "passwordRequired": true, + "passwordMinimumLength": 6, + "passwordRequiredType": "numericComplex", + "passwordMinutesOfInactivityBeforeLock": 1, + "passwordExpirationDays": null, + "passwordPreviousPasswordBlockCount": null, + "passwordSignInFailureCountBeforeFactoryReset": null, + "securityPreventInstallAppsFromUnknownSources": true, + "securityDisableUsbDebugging": true, + "securityRequireVerifyApps": false, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "advancedThreatProtectionRequiredSecurityLevel": "unavailable", + "securityBlockJailbrokenDevices": true, + "osMinimumVersion": null, + "osMaximumVersion": null, + "minAndroidSecurityPatchLevel": "2020-01-01", + "storageRequireEncryption": true, + "securityRequireSafetyNetAttestationBasicIntegrity": true, + "securityRequireSafetyNetAttestationCertifiedDevice": false, + "securityRequireGooglePlayServices": true, + "securityRequireUpToDateSecurityProviders": false, + "securityRequireCompanyPortalAppIntegrity": true + + } +"@ + +#################################################### + +$JSON_AndroidOwner = @" + { + "@odata.type": "#microsoft.graph.androidDeviceOwnerCompliancePolicy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "description": "1. SafetyNet device attestation: Check basic integrity\n2. Require a password to unlock mobile devices.\n3. Minimum password length: 6 Numeric\n4. Maximum minutes of inactivity before password is required: 1 Minute\n5. Require Encryption of data storage on device.\n", + "displayName": "Android_Device_Owner", + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "advancedThreatProtectionRequiredSecurityLevel": "unavailable", + "securityRequireSafetyNetAttestationBasicIntegrity": true, + "securityRequireSafetyNetAttestationCertifiedDevice": false, + "osMinimumVersion": null, + "osMaximumVersion": null, + "minAndroidSecurityPatchLevel": null, + "passwordRequired": true, + "passwordMinimumLength": 6, + "passwordMinimumLetterCharacters": null, + "passwordMinimumLowerCaseCharacters": null, + "passwordMinimumNonLetterCharacters": null, + "passwordMinimumNumericCharacters": null, + "passwordMinimumSymbolCharacters": null, + "passwordMinimumUpperCaseCharacters": null, + "passwordRequiredType": "numeric", + "passwordMinutesOfInactivityBeforeLock": 1, + "passwordExpirationDays": null, + "passwordPreviousPasswordCountToBlock": null, + "storageRequireEncryption": true + + } +"@ + +#################################################### + +$JSON_iOS = @" + { + "@odata.type": "microsoft.graph.iosCompliancePolicy", + "description": "1. Block Jailbroken devices\n2. Require a password to unlock mobile devices.\n3. Block Simple passwords\n4. Minimum password length: 6 Numeric\n5. Maximum minutes after screen lock before password is required: Immediately\n6. Maximum minutes of inactivity until screen locks: 1", + "displayName": "iOS Compliance Policy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passcodeBlockSimple": true, + "passcodeExpirationDays": null, + "passcodeMinimumLength": 6, + "passcodeMinutesOfInactivityBeforeLock": 0, + "passcodeMinutesOfInactivityBeforeScreenTimeout": 1, + "passcodePreviousPasscodeBlockCount": null, + "passcodeMinimumCharacterSetCount": null, + "passcodeRequiredType": "numeric", + "passcodeRequired": true, + "securityBlockJailbrokenDevices": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "low" + } +"@ + +############################### + +$JSON_Windows10 = @" + { + "@odata.type": "#microsoft.graph.windows10CompliancePolicy", + "description": "1. Require BitLocker\n2. Require Secure Boot to be enabled on the device\n3. Require code integrity\n4. Require Encryption of data storage on device.\n5. Firewall, Antivirus, Antispyware", + "displayName": "Windows 10 Compliance Policy", + "version": 7, + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passwordRequired": false, + "passwordBlockSimple": false, + "passwordRequiredToUnlockFromIdle": false, + "passwordMinutesOfInactivityBeforeLock": null, + "passwordExpirationDays": null, + "passwordMinimumLength": null, + "passwordMinimumCharacterSetCount": null, + "passwordRequiredType": "deviceDefault", + "passwordPreviousPasswordBlockCount": null, + "requireHealthyDeviceReport": false, + "osMinimumVersion": "10.0.19045.4170", + "osMaximumVersion": null, + "mobileOsMinimumVersion": null, + "mobileOsMaximumVersion": null, + "earlyLaunchAntiMalwareDriverEnabled": false, + "bitLockerEnabled": true, + "secureBootEnabled": true, + "codeIntegrityEnabled": true, + "storageRequireEncryption": true, + "activeFirewallRequired": true, + "defenderEnabled": false, + "defenderVersion": null, + "signatureOutOfDate": false, + "rtpEnabled": false, + "antivirusRequired": true, + "antiSpywareRequired": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "configurationManagerComplianceRequired": false, + "tpmRequired": false, + "validOperatingSystemBuildRanges": [] +} +"@ + +$JSON_Windows11 = @" + { + "@odata.type": "#microsoft.graph.windows10CompliancePolicy", + "description": "1. Require BitLocker\n2. Require Secure Boot to be enabled on the device\n3. Require code integrity\n4. Require Encryption of data storage on device.\n5. Firewall, Antivirus, Antispyware", + "displayName": "Windows 11 Compliance Policy", + "version": 7, + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passwordRequired": false, + "passwordBlockSimple": false, + "passwordRequiredToUnlockFromIdle": false, + "passwordMinutesOfInactivityBeforeLock": null, + "passwordExpirationDays": null, + "passwordMinimumLength": null, + "passwordMinimumCharacterSetCount": null, + "passwordRequiredType": "deviceDefault", + "passwordPreviousPasswordBlockCount": null, + "requireHealthyDeviceReport": false, + "osMinimumVersion": "10.0.22631.3296", + "osMaximumVersion": null, + "mobileOsMinimumVersion": null, + "mobileOsMaximumVersion": null, + "earlyLaunchAntiMalwareDriverEnabled": false, + "bitLockerEnabled": true, + "secureBootEnabled": true, + "codeIntegrityEnabled": true, + "storageRequireEncryption": true, + "activeFirewallRequired": true, + "defenderEnabled": false, + "defenderVersion": null, + "signatureOutOfDate": false, + "rtpEnabled": false, + "antivirusRequired": true, + "antiSpywareRequired": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "configurationManagerComplianceRequired": false, + "tpmRequired": false, + "validOperatingSystemBuildRanges": [] +} +"@ + + +$JSON = @" +{ + "@odata.type": "#microsoft.graph.termsAndConditions", + "displayName":"Customer Terms and Conditions", + "title":"Terms and Conditions", + "description":"By enrolling your device, you agree to $TenantName1 terms and conditions", + "bodyText":"I acknowledge that by enrolling my device, $TenantName1 Administrators have certain types of control. This includes visibility into corporate app inventory, email usage, and device risk. I further agree to keep company resources safe to the best of my ability and inform $TenantName1 administrators as soon as I believe my device is lost or stolen", + "acceptanceStatement":"I accept", + "version":1 +} +"@ + + +$JSON1 = @" +{ + "@odata.type": "#microsoft.graph.officeSuiteApp", + "autoAcceptEula": true, + "description":"Office 365 - Assigned", + "developer": "Microsoft", + "displayName":"Office 365 - Assigned", + "excludedApps": { + "groove": true, + "infoPath": true, + "sharePointDesigner": true + }, + "informationUrl": "", + "isFeatured": false, + "largeIcon": { + "type": "image/png", + "value": "iVBORw0KGgoAAAANSUhEUgAAAF0AAAAeCAMAAAEOZNKlAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJhUExURf////7z7/i9qfF1S/KCW/i+qv3q5P/9/PrQwfOMae1RG+s8AOxGDfBtQPWhhPvUx/759/zg1vWgg+9fLu5WIvKFX/rSxP728/nCr/FyR+tBBvOMaO1UH+1RHOs+AvSScP3u6f/+/v3s5vzg1+xFDO9kNPOOa/i7pvzj2/vWyes9Af76+Pzh2PrTxf/6+f7y7vOGYexHDv3t5+1SHfi8qPOIZPvb0O1NFuxDCe9hMPSVdPnFs/3q4/vaz/STcu5VIe5YJPWcfv718v/9/e1MFfF4T/F4TvF2TP3o4exECvF0SexIEPONavzn3/vZze1QGvF3Te5dK+5cKvrPwPrQwvKAWe1OGPexmexKEveulfezm/BxRfamiuxLE/apj/zf1e5YJfSXd/OHYv3r5feznPakiPze1P7x7f739f3w6+xJEfnEsvWdf/Wfge1LFPe1nu9iMvnDsfBqPOs/BPOIY/WZevJ/V/zl3fnIt/vTxuxHD+xEC+9mN+5ZJv749vBpO/KBWvBwRP/8+/SUc/etlPjArP/7+vOLZ/F7UvWae/708e1OF/aihvSWdvi8p+tABfSZefvVyPWihfSVde9lNvami+9jM/zi2fKEXvBuQvOKZvalifF5UPJ/WPSPbe9eLfrKuvvd0uxBB/7w7Pzj2vrRw/rOv+1PGfi/q/eymu5bKf3n4PnJuPBrPf3t6PWfgvWegOxCCO9nOO9oOfaskvSYePi5pPi2oPnGtO5eLPevlvKDXfrNvv739Pzd0/708O9gL+9lNfJ9VfrLu/OPbPnDsPBrPus+A/nArfarkQAAAGr5HKgAAADLdFJOU/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AvuakogAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAz5JREFUOE+tVTtu4zAQHQjppmWzwIJbEVCzpTpjbxD3grQHSOXKRXgCAT6EC7UBVAmp3KwBnmvfzNCyZTmxgeTZJsXx43B+HBHRE34ZkXgkerXFTheeiCkRrbB4UXmp4wSWz5raaQEMTM5TZwuiXoaKgV+6FsmkZQcSy0kA71yMTMGHanX+AzMMGLAQCxU1F/ZwjULPugazl82GM0NEKm/U8EqFwEkO3/EAT4grgl0nucwlk9pcpTTJ4VPA4g/Rb3yIRhhp507e9nTQmZ1OS5RO4sS7nIRPEeHXCHdkw9ZEW2yVE5oIS7peD58Avs7CN+PVCmHh21oOqBdjDzIs+FldPJ74TFESUSJEfVzy9U/dhu+AuOT6eBp6gGKyXEx8euO450ZE4CMfstMFT44broWw/itkYErWXRx+fFArt9Ca9os78TFed0LVIUsmIHrwbwaw3BEOnOk94qVpQ6Ka2HjxewJnfyd6jUtGDQLdWlzmYNYLeKbbGOucJsNabCq1Yub0o92rtR+i30V2dapxYVEePXcOjeCKPnYyit7BtKeNlZqHbr+gt7i+AChWA9RsRs03pxTQc67ouWpxyESvjK5Vs3DVSy3IpkxPm5X+wZoBi+MFHWW69/w8FRhc7VBe6HAhMB2b8Q0XqDzTNZtXUMnKMjwKVaCrB/CSUL7WSx/HsdJC86lFGXwnioTeOMPjV+szlFvrZLA5VMVK4y+41l4e1xfx7Z88o4hkilRUH/qKqwNVlgDgpvYCpH3XwAy5eMCRnezIUxffVXoDql2rTHFDO+pjWnTWzAfrYXn6BFECblUpWGrvPZvBipETjS5ydM7tdXpH41ZCEbBNy/+wFZu71QO2t9pgT+iZEf657Q1vpN94PQNDxUHeKR103LV9nPVOtDikcNKO+2naCw7yKBhOe9Hm79pe8C4/CfC2wDjXnqC94kEeBU3WwN7dt/2UScXas7zDl5GpkY+M8WKv2J7fd4Ib2rGTk+jsC2cleEM7jI9veF7B0MBJrsZqfKd/81q9pR2NZfwJK2JzsmIT1Ns8jUH0UusQBpU8d2JzsHiXg1zXGLqxfitUNTDT/nUUeqDBp2HZVr+Ocqi/Ty3Rf4Jn82xxfSNtAAAAAElFTkSuQmCC" + }, + "localesToInstall": [ + "en-us" + ], + "notes": "", + "officePlatformArchitecture": "x64", + "officeSuiteAppDefaultFileFormat":"officeOpenDocumentFormat", + "owner": "Microsoft", + "privacyInformationUrl": "", + "productIds": [ + "o365ProPlusRetail" + ], + "publisher": "Microsoft", + "updateChannel": "Current", + "useSharedComputerActivation": false +} +"@ + +$mac_OfficeApps = @" +{ + "@odata.type": "#microsoft.graph.macOSOfficeSuiteApp", + "description": "MacOS Office 365 - Assigned", + "developer": "Microsoft", + "displayName": "Mac Office 365 - Assigned", + "informationUrl": "", + "isFeatured": false, + "largeIcon": { + "type": "image/png", + "value": "iVBORw0KGgoAAAANSUhEUgAAAF0AAAAeCAMAAAEOZNKlAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJhUExURf////7z7/i9qfF1S/KCW/i+qv3q5P/9/PrQwfOMae1RG+s8AOxGDfBtQPWhhPvUx/759/zg1vWgg+9fLu5WIvKFX/rSxP728/nCr/FyR+tBBvOMaO1UH+1RHOs+AvSScP3u6f/+/v3s5vzg1+xFDO9kNPOOa/i7pvzj2/vWyes9Af76+Pzh2PrTxf/6+f7y7vOGYexHDv3t5+1SHfi8qPOIZPvb0O1NFuxDCe9hMPSVdPnFs/3q4/vaz/STcu5VIe5YJPWcfv718v/9/e1MFfF4T/F4TvF2TP3o4exECvF0SexIEPONavzn3/vZze1QGvF3Te5dK+5cKvrPwPrQwvKAWe1OGPexmexKEveulfezm/BxRfamiuxLE/apj/zf1e5YJfSXd/OHYv3r5feznPakiPze1P7x7f739f3w6+xJEfnEsvWdf/Wfge1LFPe1nu9iMvnDsfBqPOs/BPOIY/WZevJ/V/zl3fnIt/vTxuxHD+xEC+9mN+5ZJv749vBpO/KBWvBwRP/8+/SUc/etlPjArP/7+vOLZ/F7UvWae/708e1OF/aihvSWdvi8p+tABfSZefvVyPWihfSVde9lNvami+9jM/zi2fKEXvBuQvOKZvalifF5UPJ/WPSPbe9eLfrKuvvd0uxBB/7w7Pzj2vrRw/rOv+1PGfi/q/eymu5bKf3n4PnJuPBrPf3t6PWfgvWegOxCCO9nOO9oOfaskvSYePi5pPi2oPnGtO5eLPevlvKDXfrNvv739Pzd0/708O9gL+9lNfJ9VfrLu/OPbPnDsPBrPus+A/nArfarkQAAAGr5HKgAAADLdFJOU/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AvuakogAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAz5JREFUOE+tVTtu4zAQHQjppmWzwIJbEVCzpTpjbxD3grQHSOXKRXgCAT6EC7UBVAmp3KwBnmvfzNCyZTmxgeTZJsXx43B+HBHRE34ZkXgkerXFTheeiCkRrbB4UXmp4wSWz5raaQEMTM5TZwuiXoaKgV+6FsmkZQcSy0kA71yMTMGHanX+AzMMGLAQCxU1F/ZwjULPugazl82GM0NEKm/U8EqFwEkO3/EAT4grgl0nucwlk9pcpTTJ4VPA4g/Rb3yIRhhp507e9nTQmZ1OS5RO4sS7nIRPEeHXCHdkw9ZEW2yVE5oIS7peD58Avs7CN+PVCmHh21oOqBdjDzIs+FldPJ74TFESUSJEfVzy9U/dhu+AuOT6eBp6gGKyXEx8euO450ZE4CMfstMFT44broWw/itkYErWXRx+fFArt9Ca9os78TFed0LVIUsmIHrwbwaw3BEOnOk94qVpQ6Ka2HjxewJnfyd6jUtGDQLdWlzmYNYLeKbbGOucJsNabCq1Yub0o92rtR+i30V2dapxYVEePXcOjeCKPnYyit7BtKeNlZqHbr+gt7i+AChWA9RsRs03pxTQc67ouWpxyESvjK5Vs3DVSy3IpkxPm5X+wZoBi+MFHWW69/w8FRhc7VBe6HAhMB2b8Q0XqDzTNZtXUMnKMjwKVaCrB/CSUL7WSx/HsdJC86lFGXwnioTeOMPjV+szlFvrZLA5VMVK4y+41l4e1xfx7Z88o4hkilRUH/qKqwNVlgDgpvYCpH3XwAy5eMCRnezIUxffVXoDql2rTHFDO+pjWnTWzAfrYXn6BFECblUpWGrvPZvBipETjS5ydM7tdXpH41ZCEbBNy/+wFZu71QO2t9pgT+iZEf657Q1vpN94PQNDxUHeKR103LV9nPVOtDikcNKO+2naCw7yKBhOe9Hm79pe8C4/CfC2wDjXnqC94kEeBU3WwN7dt/2UScXas7zDl5GpkY+M8WKv2J7fd4Ib2rGTk+jsC2cleEM7jI9veF7B0MBJrsZqfKd/81q9pR2NZfwJK2JzsmIT1Ns8jUH0UusQBpU8d2JzsHiXg1zXGLqxfitUNTDT/nUUeqDBp2HZVr+Ocqi/Ty3Rf4Jn82xxfSNtAAAAAElFTkSuQmCC" + }, + "notes": "", + "owner": "Microsoft", + "privacyInformationUrl": "", + "publisher": "Microsoft" +} +"@ + +################################################## + +$Outlook = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Outlook", + "description": "Microsoft Outlook", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.outlook&hl=en", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Excel = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Excel", + "description": "Microsoft Excel", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.excel&hl=en", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Teams = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Teams", + "description": "Microsoft Teams", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.teams", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$OneDrive = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "OneDrive", + "description": "Microsoft OneDrive", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.skydrive", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Word = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Word", + "description": "Microsoft Word", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.word", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$PowerPoint = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft PowerPoint", + "description": "Microsoft PowerPoint", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.powerpoint", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + + +$MicrosoftAuthenticator = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Authenticator-Android", + "description": "Microsoft Authenticator-Android", + "publisher": "Microsoft Corporation", + "isFeatured": true, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.azure.authenticator&hl=en_US", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + + + +$iOS = @" +{ + "@odata.type": "#microsoft.graph.iosManagedAppProtection", + "displayName": "iOS_App_Protection Policy", + "description": "", + "periodOfflineBeforeAccessCheck": "PT12H", + "periodOnlineBeforeAccessCheck": "PT30M", + "allowedInboundDataTransferSources": "managedApps", + "allowedOutboundDataTransferDestinations": "managedApps", + "organizationalCredentialsRequired": false, + "allowedOutboundClipboardSharingLevel": "managedApps", + "dataBackupBlocked": true, + "deviceComplianceRequired": true, + "managedBrowserToOpenLinksRequired": false, + "saveAsBlocked": true, + "periodOfflineBeforeWipeIsEnforced": "P90D", + "pinRequired": true, + "maximumPinRetries": 5, + "simplePinBlocked": false, + "minimumPinLength": 4, + "pinCharacterSet": "numeric", + "periodBeforePinReset": "PT0S", + "allowedDataStorageLocations": [ + "oneDriveForBusiness" + ], + "contactSyncBlocked": false, + "printBlocked": true, + "fingerprintBlocked": false, + "disableAppPinIfDevicePinIsSet": false, + "appActionIfDeviceComplianceRequired": "block", + "appActionIfMaximumPinRetriesExceeded": "block", + "allowedOutboundClipboardSharingExceptionLength": 0, + "notificationRestriction": "allow", + "previousPinBlockCount": 0, + "managedBrowser": "notConfigured", + "maximumAllowedDeviceThreatLevel": "notConfigured", + "mobileThreatDefenseRemediationAction": "block", + "blockDataIngestionIntoOrganizationDocuments": false, + "allowedDataIngestionLocations": [ + "oneDriveForBusiness", + "sharePoint", + "camera" + ], + "targetedAppManagementLevels": "unmanaged", + "appDataEncryptionType": "whenDeviceLocked", + "deployedAppCount": 9, + "faceIdBlocked": false, + "appActionIfIosDeviceModelNotAllowed": "block", + "thirdPartyKeyboardsBlocked": false, + "filterOpenInToOnlyManagedApps": false, + "disableProtectionOfManagedOutboundOpenInData": false, + "protectInboundDataFromUnknownSources": false, + "exemptedAppProtocols": [ + { + "name": "Default", + "value": "tel;telprompt;skype;app-settings;calshow;itms;itmss;itms-apps;itms-appss;itms-services;" + } + ], + "apps": [ + { + "id": "com.microsoft.office.outlook.ios", + "version": "-518090279", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.outlook" + } + }, + { + "id": "com.microsoft.office.powerpoint.ios", + "version": "-740777841", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.powerpoint" + } + }, + { + "id": "com.microsoft.office.word.ios", + "version": "922692278", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.word" + } + }, + { + "id": "com.microsoft.onenote.ios", + "version": "107156768", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.onenote" + } + }, + { + "id": "com.microsoft.plannermobile.ios", + "version": "-175532278", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.plannermobile" + } + }, + { + "id": "com.microsoft.sharepoint.ios", + "version": "-585639021", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.sharepoint" + } + }, + { + "id": "com.microsoft.skydrive.ios", + "version": "-108719121", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.skydrive" + } + }, + { + "id": "com.microsoft.skype.teams.ios", + "version": "-1040529574", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.skype.teams" + } + }, + { + "id": "com.microsoft.stream.ios", + "version": "126860698", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.stream" + } + } + ] +} +"@ + +#################################################### + +$Android = @" +{ + "@odata.type": "#microsoft.graph.androidManagedAppProtection", + "displayName": "Android_Protection_Policy1", + "description": "", + "periodOfflineBeforeAccessCheck": "PT12H", + "periodOnlineBeforeAccessCheck": "PT30M", + "allowedInboundDataTransferSources": "managedApps", + "allowedOutboundDataTransferDestinations": "managedApps", + "organizationalCredentialsRequired": false, + "allowedOutboundClipboardSharingLevel": "managedApps", + "dataBackupBlocked": true, + "deviceComplianceRequired": true, + "managedBrowserToOpenLinksRequired": false, + "saveAsBlocked": true, + "periodOfflineBeforeWipeIsEnforced": "P90D", + "pinRequired": true, + "maximumPinRetries": 5, + "simplePinBlocked": false, + "minimumPinLength": 4, + "pinCharacterSet": "numeric", + "periodBeforePinReset": "PT0S", + "allowedDataStorageLocations": [], + "contactSyncBlocked": false, + "printBlocked": true, + "fingerprintBlocked": false, + "disableAppPinIfDevicePinIsSet": false, + "minimumRequiredOsVersion": null, + "minimumWarningOsVersion": null, + "minimumRequiredAppVersion": null, + "minimumWarningAppVersion": null, + "minimumWipeOsVersion": null, + "minimumWipeAppVersion": null, + "appActionIfDeviceComplianceRequired": "block", + "appActionIfMaximumPinRetriesExceeded": "block", + "pinRequiredInsteadOfBiometricTimeout": "PT30M", + "allowedOutboundClipboardSharingExceptionLength": 0, + "notificationRestriction": "allow", + "previousPinBlockCount": 0, + "managedBrowser": "notConfigured", + "maximumAllowedDeviceThreatLevel": "notConfigured", + "mobileThreatDefenseRemediationAction": "block", + "blockDataIngestionIntoOrganizationDocuments": false, + "allowedDataIngestionLocations": [ + "oneDriveForBusiness", + "sharePoint", + "camera" + ], + "appActionIfUnableToAuthenticateUser": null, + "targetedAppManagementLevels": "unmanaged", + "screenCaptureBlocked": true, + "disableAppEncryptionIfDeviceEncryptionIsEnabled": false, + "encryptAppData": true, + "deployedAppCount": 9, + "minimumRequiredPatchVersion": "0000-00-00", + "minimumWarningPatchVersion": "0000-00-00", + "minimumWipePatchVersion": "0000-00-00", + "allowedAndroidDeviceManufacturers": null, + "appActionIfAndroidDeviceManufacturerNotAllowed": "block", + "requiredAndroidSafetyNetDeviceAttestationType": "none", + "appActionIfAndroidSafetyNetDeviceAttestationFailed": "block", + "requiredAndroidSafetyNetAppsVerificationType": "none", + "appActionIfAndroidSafetyNetAppsVerificationFailed": "block", + "keyboardsRestricted": false, + "allowedAndroidDeviceModels": [], + "appActionIfAndroidDeviceModelNotAllowed": "block", + "exemptedAppPackages": [], + "approvedKeyboards": [], + "apps": [ + { + "id": "com.microsoft.office.excel.android", + "version": "-1789826587", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.excel" + } + }, + { + "id": "com.microsoft.office.onenote.android", + "version": "186482170", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.onenote" + } + }, + { + "id": "com.microsoft.office.outlook.android", + "version": "1146701235", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.outlook" + } + }, + { + "id": "com.microsoft.office.powerpoint.android", + "version": "1411665537", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.powerpoint" + } + }, + { + "id": "com.microsoft.office.word.android", + "version": "2122351424", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.word" + } + }, + { + "id": "com.microsoft.planner.android", + "version": "-1658524342", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.planner" + } + }, + { + "id": "com.microsoft.sharepoint.android", + "version": "84773357", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.sharepoint" + } + }, + { + "id": "com.microsoft.skydrive.android", + "version": "1887770705", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.skydrive" + } + }, + { + "id": "com.microsoft.teams.android", + "version": "1900143244", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.teams" + } + } + ] +} +"@ + +################################################## + +$EnterpriseDomain = Get-EnterpriseDomain +$TenantName1 = Get-TenantName + +#################################################### + +$ManagedAppPolicy_WIP = @" +{ + "description": "", + "displayName": "Win10 WIP WE Policy - Assigned", + "enforcementLevel": "encryptAuditAndBlock", + "enterpriseDomain": "$EnterpriseDomain", + "enterpriseProtectedDomainNames": [], + "protectionUnderLockConfigRequired": false, + "dataRecoveryCertificate": null, + "revokeOnUnenrollDisabled": false, + "revokeOnMdmHandoffDisabled": false, + "rightsManagementServicesTemplateId": null, + "azureRightsManagementServicesAllowed": false, + "iconsVisible": true, + "protectedApps": [ + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft Teams", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "teams.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft OneDrive", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "onedrive.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Company Portal", + "productName": "Microsoft.CompanyPortal", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "OneNote", + "productName": "Microsoft.Office.OneNote", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "PowerPoint Mobile", + "productName": "Microsoft.Office.PowerPoint", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Excel Mobile", + "productName": "Microsoft.Office.Excel", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Word Mobile", + "productName": "Microsoft.Office.Word", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + } + ], + "exemptApps": [], + "protectedAppLockerFiles": [ + { + "displayName": "Office-365-ProPlus-1708-Allowed.xml", + "fileHash": "4107a857-0e0b-483f-8ff5-bb3ed095b434", + "file": "PEFwcExvY2tlclBvbGljeSBWZXJzaW9uPSIxIj4NCiAgPFJ1bGVDb2xsZWN0aW9uIFR5cGU9IkV4ZSIgRW5mb3JjZW1lbnRNb2RlPSJFbmFibGVkIj4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImQ2NjMyNzZjLTU0NGUtNGRlYS1iNzVjLTc1ZmMyZDRlMDI2NiIgTmFtZT0iTFlOQy5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQoJCSAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KCTwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIyMTk1NzFiNC1hMjU3LTRiNGMtYjg1Ni1lYmYwNjgxZWUwZjAiIE5hbWU9IkxZTkM5OS5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI1MzhkNDQzYS05ZmRiLTQ5MWUtOGJjMy1lNzhkYjljMzEwYzciIE5hbWU9Ik9ORU5PVEVNLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFTS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjZlZjA3YTkyLWYxZGItNDg4MC04YjQwLWRkMzliNzQzYTQ4NSIgTmFtZT0iV0lOV09SRC5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iN2Q3NGQxYWQtYTYwYi00ZWE4LWJiNjAtM2Q0MDQ4ODZkMTBiIiBOYW1lPSJPTkVOT1RFLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iODQwOWFhNjItMWI1Mi00ODRiLTg3ODctZDU3Y2M4NTI1ZTIxIiBOYW1lPSJPQ1BVQk1HUi5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJPQ1BVQk1HUi5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImU2Y2NjY2QxLWE2MGYtNGNiNC04YjRiLTkwM2M3MDk3NmI1MCIgTmFtZT0iUE9XRVJQTlQuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iUE9XRVJQTlQuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJlYzc5ZWJiZS0zMTY5LTRhMjYtYWY1ZS1lMDc2YzkwOTY0OTUiIE5hbWU9Ik1TT1NZTkMuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iTVNPU1lOQy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYwYjM1MjZiLTE0ZTctNDQyOC05NzAzLTRkZmYyZWE0MDAwZCIgTmFtZT0iVUNNQVBJLkVYRSwgdmVyc2lvbiAxNi4wLjc4NzAuMjAyMCBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPRkZJQ0UgMjAxNiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IlVDTUFQSS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYxYmVkY2IxLWNhMzQtNDdiNS04NmM4LTI1NjU0YjE3OGNiOSIgTmFtZT0iT1VUTE9PSy5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT1VUTE9PSywgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImY5NjFlODQ2LTNmZTctNDY0Yy05NTA4LTAzNTMzN2QxZTg2OCIgTmFtZT0iRVhDRUwuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iRVhDRUwuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJkNzAzMzE5Yy0wYzllLTQ0NjctYWQwOS03MTMzMDdlMzBkZjYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImRlOWYzNDYxLTY4NTYtNDA1ZC05NjI0LWE4MGNhNzAxZjZjYiIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImFkZTFiODI4LTcwNTUtNDdmYy05OWJjLTQzMmNmN2QxMjA5ZSIgTmFtZT0iMjAwNyBNSUNST1NPRlQgT0ZGSUNFIFNZU1RFTSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9IjIwMDcgTUlDUk9TT0ZUIE9GRklDRSBTWVNURU0iIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYmIzODc3YjYtZjFhZC00YzgzLTk2ZTItZDZiZjc1ZTMzY2JmIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iMjM2MzVlZWYtYjFlMy00ZGEyLTg4NTktMDYzOGVjNjA3YjM4IiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iZDJkMDUyMGEtNTdkZS00YmQ1LWJjZDktYjNkNDcyMjFmODdhIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IkVYQ0VMLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik1TT1NZTkMuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik9DUFVCTUdSLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJQT1dFUlBOVC5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iVUNNQVBJLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvRXhjZXB0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYTMzNjVkZmYtNjA1Mi00MWE4LTgyYTYtMzQ0NDRlYzI1OTUzIiBOYW1lPSJNSUNST1NPRlQgT05FTk9URSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgICA8RXhjZXB0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9ORU5PVEUiIEJpbmFyeU5hbWU9Ik9ORU5PVEUuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT05FTk9URSIgQmluYXJ5TmFtZT0iT05FTk9URU0uRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9FeGNlcHRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIwYmE0YzE1MC0xY2IzLTRjYjktYWIwMi0yNjUyNzQ0MTk0MDkiIE5hbWU9Ik1JQ1JPU09GVCBPVVRMT09LLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9VVExPT0siIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0V4Y2VwdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjY3MTRmODVmLWFkZGEtNGVjNy05NDdjLTc1NTJmN2Q5NDYyNSIgTmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4JDQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI3NGFiMzY4Zi1hMTQ3LTRjZjktODBjMy01M2ZiNjY5YzQ4MzYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSU5GT1BBVEgsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIElORk9QQVRIIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9Ijk3NjlkMjA5LTg1ZjgtNDM4OS1iZGM2LWRkY2EzMGYxYzY3MSIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSBIRUxQIFZJRVdFUiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSEVMUCBWSUVXRVIiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KCTwvUnVsZUNvbGxlY3Rpb24+DQo8L0FwcExvY2tlclBvbGljeT4=", + "id": "0c5c4541-87a8-478b-b9fc-4206d0f421f9", + "@odata.type": "#microsoft.graph.windowsInformationProtectionAppLockerFile" + } + ], + "exemptAppLockerFiles": [], + "enterpriseIPRanges": [], + "enterpriseIPRangesAreAuthoritative": false, + "enterpriseProxyServers": [], + "enterpriseInternalProxyServers": [], + "enterpriseProxyServersAreAuthoritative": false, + "neutralDomainResources": [], + "windowsHelloForBusinessBlocked": false, + "pinMinimumLength": 4, + "pinUppercaseLetters": "notAllow", + "pinLowercaseLetters": "notAllow", + "pinSpecialCharacters": "notAllow", + "pinExpirationDays": 0, + "numberOfPastPinsRemembered": 0, + "passwordMaximumAttemptCount": 0, + "minutesOfInactivityBeforeDeviceLock": 0, + "mdmEnrollmentUrl": "https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc", + "@odata.type": "#microsoft.graph.windowsInformationProtectionPolicy" +} +"@ + +#################################################### + +$ManagedAppPolicy_WIP_MDM = @" +{ + "description": "", + "displayName": "Win10 WIP MDM Policy - Assigned", + "enforcementLevel": "encryptAuditAndBlock", + "enterpriseDomain": "$EnterpriseDomain", + "enterpriseProtectedDomainNames": [], + "protectionUnderLockConfigRequired": false, + "dataRecoveryCertificate": null, + "revokeOnUnenrollDisabled": false, + "rightsManagementServicesTemplateId": null, + "azureRightsManagementServicesAllowed": false, + "iconsVisible": true, + "protectedApps": [ + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft Teams", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "teams.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft OneDrive", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "onedrive.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Company Portal", + "productName": "Microsoft.CompanyPortal", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "OneNote", + "productName": "Microsoft.Office.OneNote", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "PowerPoint Mobile", + "productName": "Microsoft.Office.PowerPoint", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Excel Mobile", + "productName": "Microsoft.Office.Excel", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Word Mobile", + "productName": "Microsoft.Office.Word", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + } + ], + "exemptApps": [], + "protectedAppLockerFiles": [ + { + "displayName": "Office-365-ProPlus-1708-Allowed.xml", + "fileHash": "2eb106c8-234b-4253-af44-63f522bfe222", + "file": "PEFwcExvY2tlclBvbGljeSBWZXJzaW9uPSIxIj4NCiAgPFJ1bGVDb2xsZWN0aW9uIFR5cGU9IkV4ZSIgRW5mb3JjZW1lbnRNb2RlPSJFbmFibGVkIj4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImQ2NjMyNzZjLTU0NGUtNGRlYS1iNzVjLTc1ZmMyZDRlMDI2NiIgTmFtZT0iTFlOQy5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQoJCSAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KCTwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIyMTk1NzFiNC1hMjU3LTRiNGMtYjg1Ni1lYmYwNjgxZWUwZjAiIE5hbWU9IkxZTkM5OS5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI1MzhkNDQzYS05ZmRiLTQ5MWUtOGJjMy1lNzhkYjljMzEwYzciIE5hbWU9Ik9ORU5PVEVNLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFTS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjZlZjA3YTkyLWYxZGItNDg4MC04YjQwLWRkMzliNzQzYTQ4NSIgTmFtZT0iV0lOV09SRC5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iN2Q3NGQxYWQtYTYwYi00ZWE4LWJiNjAtM2Q0MDQ4ODZkMTBiIiBOYW1lPSJPTkVOT1RFLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iODQwOWFhNjItMWI1Mi00ODRiLTg3ODctZDU3Y2M4NTI1ZTIxIiBOYW1lPSJPQ1BVQk1HUi5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJPQ1BVQk1HUi5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImU2Y2NjY2QxLWE2MGYtNGNiNC04YjRiLTkwM2M3MDk3NmI1MCIgTmFtZT0iUE9XRVJQTlQuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iUE9XRVJQTlQuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJlYzc5ZWJiZS0zMTY5LTRhMjYtYWY1ZS1lMDc2YzkwOTY0OTUiIE5hbWU9Ik1TT1NZTkMuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iTVNPU1lOQy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYwYjM1MjZiLTE0ZTctNDQyOC05NzAzLTRkZmYyZWE0MDAwZCIgTmFtZT0iVUNNQVBJLkVYRSwgdmVyc2lvbiAxNi4wLjc4NzAuMjAyMCBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPRkZJQ0UgMjAxNiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IlVDTUFQSS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYxYmVkY2IxLWNhMzQtNDdiNS04NmM4LTI1NjU0YjE3OGNiOSIgTmFtZT0iT1VUTE9PSy5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT1VUTE9PSywgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImY5NjFlODQ2LTNmZTctNDY0Yy05NTA4LTAzNTMzN2QxZTg2OCIgTmFtZT0iRVhDRUwuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iRVhDRUwuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJkNzAzMzE5Yy0wYzllLTQ0NjctYWQwOS03MTMzMDdlMzBkZjYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImRlOWYzNDYxLTY4NTYtNDA1ZC05NjI0LWE4MGNhNzAxZjZjYiIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImFkZTFiODI4LTcwNTUtNDdmYy05OWJjLTQzMmNmN2QxMjA5ZSIgTmFtZT0iMjAwNyBNSUNST1NPRlQgT0ZGSUNFIFNZU1RFTSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9IjIwMDcgTUlDUk9TT0ZUIE9GRklDRSBTWVNURU0iIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYmIzODc3YjYtZjFhZC00YzgzLTk2ZTItZDZiZjc1ZTMzY2JmIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iMjM2MzVlZWYtYjFlMy00ZGEyLTg4NTktMDYzOGVjNjA3YjM4IiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iZDJkMDUyMGEtNTdkZS00YmQ1LWJjZDktYjNkNDcyMjFmODdhIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IkVYQ0VMLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik1TT1NZTkMuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik9DUFVCTUdSLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJQT1dFUlBOVC5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iVUNNQVBJLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvRXhjZXB0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYTMzNjVkZmYtNjA1Mi00MWE4LTgyYTYtMzQ0NDRlYzI1OTUzIiBOYW1lPSJNSUNST1NPRlQgT05FTk9URSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgICA8RXhjZXB0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9ORU5PVEUiIEJpbmFyeU5hbWU9Ik9ORU5PVEUuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT05FTk9URSIgQmluYXJ5TmFtZT0iT05FTk9URU0uRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9FeGNlcHRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIwYmE0YzE1MC0xY2IzLTRjYjktYWIwMi0yNjUyNzQ0MTk0MDkiIE5hbWU9Ik1JQ1JPU09GVCBPVVRMT09LLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9VVExPT0siIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0V4Y2VwdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjY3MTRmODVmLWFkZGEtNGVjNy05NDdjLTc1NTJmN2Q5NDYyNSIgTmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4JDQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI3NGFiMzY4Zi1hMTQ3LTRjZjktODBjMy01M2ZiNjY5YzQ4MzYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSU5GT1BBVEgsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIElORk9QQVRIIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9Ijk3NjlkMjA5LTg1ZjgtNDM4OS1iZGM2LWRkY2EzMGYxYzY3MSIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSBIRUxQIFZJRVdFUiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSEVMUCBWSUVXRVIiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KCTwvUnVsZUNvbGxlY3Rpb24+DQo8L0FwcExvY2tlclBvbGljeT4=", + "id": "4e49ffae-f006-46b9-b74d-785565f7efb4", + "@odata.type": "#microsoft.graph.windowsInformationProtectionAppLockerFile" + } + ], + "exemptAppLockerFiles": [], + "enterpriseNetworkDomainNames": [], + "enterpriseIPRanges": [], + "enterpriseIPRangesAreAuthoritative": false, + "enterpriseProxyServers": [], + "enterpriseInternalProxyServers": [], + "enterpriseProxyServersAreAuthoritative": false, + "neutralDomainResources": [], + "@odata.type": "#microsoft.graph.mdmWindowsInformationProtectionPolicy" +} +"@ + +#################################################### + +$windows_endpoint = @" + +{ + "@odata.type": "#microsoft.graph.windows10EndpointProtectionConfiguration", + "description": "Silently configure BitLocker Encryption", + "displayName": "Windows_Endpoint_Profile", + "version": 1, + "firewallBlockStatefulFTP": false, + "firewallIdleTimeoutForSecurityAssociationInSeconds": null, + "firewallPreSharedKeyEncodingMethod": "deviceDefault", + "firewallIPSecExemptionsAllowNeighborDiscovery": false, + "firewallIPSecExemptionsAllowICMP": false, + "firewallIPSecExemptionsAllowRouterDiscovery": false, + "firewallIPSecExemptionsAllowDHCP": false, + "firewallCertificateRevocationListCheckMethod": "deviceDefault", + "firewallMergeKeyingModuleSettings": false, + "firewallPacketQueueingMethod": "deviceDefault", + "firewallProfileDomain": null, + "firewallProfilePublic": null, + "firewallProfilePrivate": null, + "defenderAttackSurfaceReductionExcludedPaths": [], + "defenderGuardedFoldersAllowedAppPaths": [], + "defenderAdditionalGuardedFolders": [], + "defenderExploitProtectionXml": null, + "defenderExploitProtectionXmlFileName": null, + "defenderSecurityCenterBlockExploitProtectionOverride": false, + "appLockerApplicationControl": "notConfigured", + "smartScreenEnableInShell": false, + "smartScreenBlockOverrideForFiles": false, + "applicationGuardEnabled": false, + "applicationGuardBlockFileTransfer": "notConfigured", + "applicationGuardBlockNonEnterpriseContent": false, + "applicationGuardAllowPersistence": false, + "applicationGuardForceAuditing": false, + "applicationGuardBlockClipboardSharing": "notConfigured", + "applicationGuardAllowPrintToPDF": false, + "applicationGuardAllowPrintToXPS": false, + "applicationGuardAllowPrintToLocalPrinters": false, + "applicationGuardAllowPrintToNetworkPrinters": false, + "bitLockerDisableWarningForOtherDiskEncryption": true, + "bitLockerEnableStorageCardEncryptionOnMobile": false, + "bitLockerEncryptDevice": true, + "bitLockerRemovableDrivePolicy": { + "encryptionMethod": null, + "requireEncryptionForWriteAccess": false, + "blockCrossOrganizationWriteAccess": false + } + } +"@ + + + + +#################################################### + +# Setting AAD Group + +$AADGroup = Read-Host -Prompt "Enter the Azure AD Group name where policies will be assigned" + +$TargetGroupId = (get-AADGroup -GroupName "$AADGroup").id + + if($TargetGroupId -eq $null -or $TargetGroupId -eq ""){ + + Write-Host "AAD Group - '$AADGroup' doesn't exist, please specify a valid AAD Group..." -ForegroundColor Red + Write-Host + + + $AADGroup = Read-Host -Prompt "Enter the Azure AD Group name where policies will be assigned" + $TargetGroupId = (get-AADGroup -GroupName "$AADGroup").id + + } + +Write-Host + +############################################################################## + +Write-Host +Write-Host "Adding Terms and Conditions from JSON..." -ForegroundColor Cyan +Write-Host "Creating Terms and Conditions via Graph" +$CreateResult = Add-TermsAndConditions -JSON $JSON +write-host "Terms and Conditions created with id" $CreateResult.id + +Write-Host + +write-host "Assigning Terms and Conditions to AAD Group '$AADGroup'" -f Yellow +$Assign_Policy = Assign-TermsAndConditions -id $CreateResult.id -TargetGroupId $TargetGroupId +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" +Write-Host + +#################################################### + +Write-Host "Adding macOS Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_macOS = Add-DeviceCompliancePolicy -JSON $JSON_macOS + +Write-Host "Compliance Policy created as" $CreateResult_macOS.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_macOS = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_macOS.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_macOS.displayName)/$($CreateResult_macOS.id)" +Write-Host + +#################################################### + +Write-Host "Adding Android Work Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_AndroidWork = Add-DeviceCompliancePolicy -JSON $JSON_AndroidWork + +Write-Host "Compliance Policy created as" $CreateResult_AndroidWork.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_AndroidWork.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_AndroidWork.displayName)/$($CreateResult_AndroidWork.id)" +Write-Host + +#################################################### + +Write-Host "Adding Android Device Owner Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_AndroidOwner = Add-DeviceCompliancePolicy -JSON $JSON_AndroidOwner + +Write-Host "Compliance Policy created as" $CreateResult_AndroidOwner.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_AndroidOwner.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_AndroidOwner.displayName)/$($CreateResult_AndroidOwner.id)" +Write-Host + +#################################################### + +Write-Host "Adding iOS Compliance Policy from JSON..." -ForegroundColor Yellow +Write-Host + +$CreateResult_iOS = Add-DeviceCompliancePolicy -JSON $JSON_iOS + +Write-Host "Compliance Policy created as" $CreateResult_iOS.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_iOS = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_iOS.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_iOS.displayName)/$($CreateResult_iOS.id)" +Write-Host + +##################################################### + +Write-Host "Adding Windows 10 Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_Windows = Add-DeviceCompliancePolicy -JSON $JSON_Windows10 + +Write-Host "Compliance Policy created as" $CreateResult_Windows.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_Windows.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Windows.displayName)/$($CreateResult_Windows.id)" +Write-Host +##################################################### + +Write-Host "Adding Windows 11 Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_Windows = Add-DeviceCompliancePolicy -JSON $JSON_Windows11 + +Write-Host "Compliance Policy created as" $CreateResult_Windows.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_Windows.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Windows.displayName)/$($CreateResult_Windows.id)" +Write-Host +#################################################### + +Write-Host "Adding Windows 10 Endpoint Protection Profile from JSON..." -ForegroundColor Yellow + +$CreateResult_Endpoint = Add-DeviceConfigurationPolicy -JSON $windows_endpoint + +Write-Host "Device Restriction Policy created as" $CreateResult_Endpoint.id +write-host +write-host "Assigning Windows 10 Endpoint Protection Profile to AAD Group '$AADGroup'" -f Cyan + +$Assign_Endpoint = Add-DeviceConfigurationPolicyAssignment -ConfigurationPolicyId $CreateResult_Endpoint.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Endpoint.displayName)/$($CreateResult_Endpoint.id)" +Write-Host + + +#################################################### + +Write-Host "Adding Windows 10 Update Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_UpdateProfile = Add-WindowsUpdatePolicy -JSON $JSON_10QualityUpdate + +Write-Host "Windows 10 Update Policy created as" $CreateResult_UpdateProfile.id +write-host +Write-Host "Adding Windows 11 Update Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_UpdateProfile = Add-WindowsUpdatePolicy -JSON $JSON_11QualityUpdate + +Write-Host "Windows 11 Update Policy created as" $CreateResult_UpdateProfile.id +write-host + + +################################################## + + +################################################################################## + +write-host "Publishing" ($JSON1 | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Application = Add-MDMApplication -JSON $JSON1 + +Write-Host "Application created as $($Create_Application.displayName)/$($create_Application.id)" + +$ApplicationId = $Create_Application.id + +$Assign_Application = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" +Write-Host "Assigned '$AADGroup' to $($Create_Application.displayName)/$($Create_Application.id) with" $Assign_Application.InstallIntent "install Intent" + +Write-Host + +# Set parameter culture for script execution +$culture = "EN-US" + +# Backup current culture +$OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture +$OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture + + +# Set new Culture for script execution +[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture +[System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture + + +################################################## + +write-host "Publishing" ($mac_OfficeApps | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_macApps = Add-MDMApplication -JSON $mac_OfficeApps + +Write-Host "Application created as $($Create_macApps.displayName)/$($create_macApps.id)" + +$ApplicationId = $Create_macApps.id + +$Assign_Application = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" +Write-Host "Assigned '$AADGroup' to $($Create_Application.displayName)/$($Create_Application.id) with" $Assign_Application.InstallIntent "install Intent" + +Write-Host + +#################################################### + +$itunesApps = Get-itunesApplication -SearchString "Microsoft" -Limit 50 + +#region Office Example +$Applications = 'Microsoft Outlook','Microsoft Excel','OneDrive','Microsoft Authenticator', 'Microsoft Teams', 'Microsoft Word', 'Microsoft PowerPoint' +#endregion + +# If application list is specified +if($Applications) { + + # Looping through applications list + foreach($Application in $Applications){ + + $itunesApp = $itunesApps.results | ? { ($_.trackName).contains("$Application") } + + # if single application count is greater than 1 loop through names + if($itunesApp.count -gt 1){ + + $itunesApp.count + write-host "More than 1 application was found in the itunes store" -f Cyan + + foreach($iapp in $itunesApp){ + + $Create_App = Add-iOSApplication -itunesApp $iApp + + $ApplicationId = $Create_App.id + + Write-Host "Creating Application" + Write-Host + Write-Host + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + } + + # Single application found, adding application + elseif($itunesApp){ + + $Create_App = Add-iOSApplication -itunesApp $itunesApp + + $ApplicationId = $Create_App.id + + Write-Host "Creating Application" + Write-Host + Write-Host + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + # if application isn't found in itunes returning doesn't exist + else { + + write-host + write-host "Application '$Application' doesn't exist" -f Red + write-host + + } + + } + +} + +# No Applications have been specified +else { + + # if there are results returned from itunes query + if($itunesApps.results){ + + write-host + write-host "Number of iOS applications to add:" $itunesApps.results.count -f Yellow + Write-Host + + # Looping through applications returned from itunes + foreach($itunesApp in $itunesApps.results){ + + $Create_App = Add-iOSApplication -itunesApp $itunesApp + + $ApplicationId = $Create_App.id + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + } + + # No applications returned from itunes + else { + + write-host + write-host "No applications found..." -f Red + write-host + + } + +} + + +################################################## + + + + + + + +Write-Host "Adding App Protection Policies to Intune..." -ForegroundColor Cyan +Write-Host + +Write-Host "Adding iOS Managed App Policy from JSON..." -ForegroundColor Yellow +Write-Host "Creating Policy via Graph" + +$CreateResult = Add-ManagedAppPolicy -Json $iOS +write-host "Policy created with id" $CreateResult.id + +$MAM_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-ManagedAppPolicy -Id $MAM_PolicyID -TargetGroupId $TargetGroupId -OS iOS +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + +write-host "Adding Android Managed App Policy from JSON..." -f Yellow +Write-Host "Creating Policy via Graph" + +$CreateResult = Add-ManagedAppPolicy -Json $Android +write-host "Policy created with id" $CreateResult.id + +$MAM_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-ManagedAppPolicy -Id $MAM_PolicyID -TargetGroupId $TargetGroupId -OS Android +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + +#################################################### +<# +Write-Host "Adding Windows Information Protection Without Enrollment Policy from JSON..." -ForegroundColor Yellow + +$CreateResult = Add-WindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_WIP + +write-host "Policy created with id" $CreateResult.id + +$WIP_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + +Write-Host "Adding Windows Information Protection MDM Policy from JSON..." -ForegroundColor Yellow + +$CreateResult = Add-MDMWindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_WIP_MDM + +write-host "Policy created with id" $CreateResult.id + +$WIP_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-MDMWindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host +#> +############################################################## + +write-host "Publishing Android" ($MicrosoftAuthenticator | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_MicrosoftAuthenticator = Add-AndroidApplication -JSON $MicrosoftAuthenticator + +Write-Host "Application created as $($Create_MicrosoftAuthenticator.displayName)/$($create_MicrosoftAuthenticator.id)" +Write-Host + + + $ApplicationId1 = $Create_MicrosoftAuthenticator.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + + +################################################## + +write-host "Publishing Android" ($Outlook | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Outlook = Add-AndroidApplication -JSON $Outlook + +Write-Host "Application created as $($Create_Outlook.displayName)/$($create_Outlook.id)" +Write-Host + + + $ApplicationId1 = $Create_Outlook.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_Outlook.displayName)/$($create_Outlook.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + +################################################## + +write-host "Publishing Android" ($Word| ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Word = Add-AndroidApplication -JSON $Word + +Write-Host "Application created as $($Create_Word.displayName)/$($create_Word.id)" +Write-Host + + $ApplicationId1 = $Create_Word.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($Excel | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Excel = Add-AndroidApplication -JSON $Excel +Write-Host "Application created as $($Create_Excel.displayName)/$($create_Excel.id)" +Write-Host + + $ApplicationId1 = $Create_Excel.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + +################################################## + +write-host "Publishing Android" ($Teams | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Teams = Add-AndroidApplication -JSON $Teams + +Write-Host "Application created as $($Create_Teams.displayName)/$($create_Teams.id)" +Write-Host + + $ApplicationId1 = $Create_Teams.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($OneDrive | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_OneDrive = Add-AndroidApplication -JSON $OneDrive + +Write-Host "Application created as $($Create_OneDrive.displayName)/$($create_OneDrive.id)" +Write-Host + + $ApplicationId1 = $Create_OneDrive.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($PowerPoint | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_PowerPoint = Add-AndroidApplication -JSON $PowerPoint + +Write-Host "Application created as $($Create_PowerPoint.displayName)/$($create_PowerPoint.id)" +Write-Host + + $ApplicationId1 = $Create_PowerPoint.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + +$block = @" + + __ __ .______ _______ ___ .___________. _______ _______ .______ ____ ____ __ __ ___ __ +| | | | | _ \ | \ / \ | || ____|| \ | _ \ \ \ / / | | | | / \ | | +| | | | | |_) | | .--. | / ^ \ `---| |----`| |__ | .--. | | |_) | \ \/ / | |__| | / ^ \ | | ______ +| | | | | ___/ | | | | / /_\ \ | | | __| | | | | | _ < \_ _/ | __ | / /_\ \ | | |______| +| `--' | | | | '--' | / _____ \ | | | |____ | '--' | | |_) | | | | | | | / _____ \ | `----. + \______/ | _| |_______/ /__/ \__\ |__| |_______||_______/ |______/ |__| |__| |__| /__/ \__\ |_______| + +.___________. __ __ ___ .__ __. __ ___ _______. .___________..___ ___. __ .__ __. __ __ _______. __ +| || | | | / \ | \ | | | |/ / / | | || \/ | | | | \ | | | | | | / || | +`---| |----`| |__| | / ^ \ | \| | | ' / | (----` `---| |----`| \ / | | | | \| | | | | | | (----`| | + | | | __ | / /_\ \ | . ` | | < \ \ | | | |\/| | | | | . ` | | | | | \ \ | | + | | | | | | / _____ \ | |\ | | . \ .----) | __ | | | | | | | | | |\ | | `--' | .----) | |__| + |__| |__| |__| /__/ \__\ |__| \__| |__|\__\ |_______/ (_ ) |__| |__| |__| |__| |__| \__| \______/ |_______/ (__) + |/ + +"@ + +Write-Host $block -ForegroundColor Cyan + + + + From 19a3a7bb24d59dffb02a502c6f8c120e24ac7f77 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:26:24 -0400 Subject: [PATCH 004/107] Add files via upload --- Intune-AutomationRunbookv1_0.ps1 | 232 ++++++++++++++++--------------- 1 file changed, 123 insertions(+), 109 deletions(-) diff --git a/Intune-AutomationRunbookv1_0.ps1 b/Intune-AutomationRunbookv1_0.ps1 index f95e515..40bcc1b 100644 --- a/Intune-AutomationRunbookv1_0.ps1 +++ b/Intune-AutomationRunbookv1_0.ps1 @@ -1,6 +1,7 @@ <# .COPYRIGHT -Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +Original Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +Updates made to modernize script and add other customizations. See LICENSE in the project root for license information. #> @@ -9,13 +10,14 @@ See LICENSE in the project root for license information. After you run this script, you will have 1.White labeled Terms and Conditions -2.A device compliance policy for: Windows, iOS/ipad, Android for Work, Android Device Owner, macOS +2.A device compliance policy for: Windows10, Windows 11, iOS/ipad, Android for Work, Android Device Owner, macOS 3.A Windows 10 Endpoint Protection Profile for Windows Devices for Silent Bitlocker Encryption -4.365 Business Suite to Windows 10 and MacOS +4.M365 Apps to Windows 10 and MacOS 5.Office Suite for iOS and Android (Word, PowerPoint, Excel, Teams, OneDrive, Outlook) 6.Microsoft Authenticator pushed out as a required App for iOS and Android devices 7.App Protection Policies for Android and iOS without enrollment 8.Windows Information Protection policies with or without enrollment (2 policies) +9.Windows Update Rings for Windows 10 and Windows 11 #> @@ -1650,7 +1652,7 @@ param ) $graphApiVersion = "Beta" -$Resource = "deviceAppManagement/windowsInformationProtectionPolicies" +$Resource = "deviceAppManagement/windowsManagedAppProtections" try { @@ -1662,6 +1664,68 @@ $Resource = "deviceAppManagement/windowsInformationProtectionPolicies" else { + Test-JSON -JSON $JSON + write-host "Adding JSON information to Windows Information Protection Policy: $JSON" + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +###################################################### + +Function Add-WindowsBaselineSecurity(){ + +<# +.SYNOPSIS +This function is used to add a Windows Baseline Security policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Baseline Security policy +.EXAMPLE +Add-WindowsBaselineSecurity -JSON $JSON +Adds a Windows Baseline Security policy in Intune +.NOTES +NAME: Add-WindowsBaselineSecurity +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/intents" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Baseline Security Policy..." -f Red + + } + + else { + Test-JSON -JSON $JSON $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" @@ -2145,7 +2209,7 @@ $global:authToken = Get-AuthToken -User $User #endregion #################################################### -<# Configurations using JSON #> +<# Configurations using JSON Files #> #################################################### $JSON_10QualityUpdate = @" @@ -3076,108 +3140,53 @@ $ManagedAppPolicy_WIP = @" "@ #################################################### - -$ManagedAppPolicy_WIP_MDM = @" +$ManagedAppPolicy_Windows = @" { - "description": "", - "displayName": "Win10 WIP MDM Policy - Assigned", - "enforcementLevel": "encryptAuditAndBlock", - "enterpriseDomain": "$EnterpriseDomain", - "enterpriseProtectedDomainNames": [], - "protectionUnderLockConfigRequired": false, - "dataRecoveryCertificate": null, - "revokeOnUnenrollDisabled": false, - "rightsManagementServicesTemplateId": null, - "azureRightsManagementServicesAllowed": false, - "iconsVisible": true, - "protectedApps": [ - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", - "description": "", - "displayName": "Microsoft Teams", - "productName": "*", - "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false, - "binaryName": "teams.exe", - "binaryVersionLow": "*", - "binaryVersionHigh": "*" - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", - "description": "", - "displayName": "Microsoft OneDrive", - "productName": "*", - "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false, - "binaryName": "onedrive.exe", - "binaryVersionLow": "*", - "binaryVersionHigh": "*" - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", - "description": "", - "displayName": "Company Portal", - "productName": "Microsoft.CompanyPortal", - "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", - "description": "", - "displayName": "OneNote", - "productName": "Microsoft.Office.OneNote", - "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", - "description": "", - "displayName": "PowerPoint Mobile", - "productName": "Microsoft.Office.PowerPoint", - "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", - "description": "", - "displayName": "Excel Mobile", - "productName": "Microsoft.Office.Excel", - "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false - }, - { - "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", - "description": "", - "displayName": "Word Mobile", - "productName": "Microsoft.Office.Word", - "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", - "denied": false - } - ], - "exemptApps": [], - "protectedAppLockerFiles": [ - { - "displayName": "Office-365-ProPlus-1708-Allowed.xml", - "fileHash": "2eb106c8-234b-4253-af44-63f522bfe222", - "file": "PEFwcExvY2tlclBvbGljeSBWZXJzaW9uPSIxIj4NCiAgPFJ1bGVDb2xsZWN0aW9uIFR5cGU9IkV4ZSIgRW5mb3JjZW1lbnRNb2RlPSJFbmFibGVkIj4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImQ2NjMyNzZjLTU0NGUtNGRlYS1iNzVjLTc1ZmMyZDRlMDI2NiIgTmFtZT0iTFlOQy5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQoJCSAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KCTwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIyMTk1NzFiNC1hMjU3LTRiNGMtYjg1Ni1lYmYwNjgxZWUwZjAiIE5hbWU9IkxZTkM5OS5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI1MzhkNDQzYS05ZmRiLTQ5MWUtOGJjMy1lNzhkYjljMzEwYzciIE5hbWU9Ik9ORU5PVEVNLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFTS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjZlZjA3YTkyLWYxZGItNDg4MC04YjQwLWRkMzliNzQzYTQ4NSIgTmFtZT0iV0lOV09SRC5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iN2Q3NGQxYWQtYTYwYi00ZWE4LWJiNjAtM2Q0MDQ4ODZkMTBiIiBOYW1lPSJPTkVOT1RFLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iODQwOWFhNjItMWI1Mi00ODRiLTg3ODctZDU3Y2M4NTI1ZTIxIiBOYW1lPSJPQ1BVQk1HUi5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJPQ1BVQk1HUi5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImU2Y2NjY2QxLWE2MGYtNGNiNC04YjRiLTkwM2M3MDk3NmI1MCIgTmFtZT0iUE9XRVJQTlQuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iUE9XRVJQTlQuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJlYzc5ZWJiZS0zMTY5LTRhMjYtYWY1ZS1lMDc2YzkwOTY0OTUiIE5hbWU9Ik1TT1NZTkMuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iTVNPU1lOQy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYwYjM1MjZiLTE0ZTctNDQyOC05NzAzLTRkZmYyZWE0MDAwZCIgTmFtZT0iVUNNQVBJLkVYRSwgdmVyc2lvbiAxNi4wLjc4NzAuMjAyMCBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPRkZJQ0UgMjAxNiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IlVDTUFQSS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYxYmVkY2IxLWNhMzQtNDdiNS04NmM4LTI1NjU0YjE3OGNiOSIgTmFtZT0iT1VUTE9PSy5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT1VUTE9PSywgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImY5NjFlODQ2LTNmZTctNDY0Yy05NTA4LTAzNTMzN2QxZTg2OCIgTmFtZT0iRVhDRUwuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iRVhDRUwuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJkNzAzMzE5Yy0wYzllLTQ0NjctYWQwOS03MTMzMDdlMzBkZjYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImRlOWYzNDYxLTY4NTYtNDA1ZC05NjI0LWE4MGNhNzAxZjZjYiIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImFkZTFiODI4LTcwNTUtNDdmYy05OWJjLTQzMmNmN2QxMjA5ZSIgTmFtZT0iMjAwNyBNSUNST1NPRlQgT0ZGSUNFIFNZU1RFTSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9IjIwMDcgTUlDUk9TT0ZUIE9GRklDRSBTWVNURU0iIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYmIzODc3YjYtZjFhZC00YzgzLTk2ZTItZDZiZjc1ZTMzY2JmIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iMjM2MzVlZWYtYjFlMy00ZGEyLTg4NTktMDYzOGVjNjA3YjM4IiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iZDJkMDUyMGEtNTdkZS00YmQ1LWJjZDktYjNkNDcyMjFmODdhIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IkVYQ0VMLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik1TT1NZTkMuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik9DUFVCTUdSLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJQT1dFUlBOVC5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iVUNNQVBJLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvRXhjZXB0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYTMzNjVkZmYtNjA1Mi00MWE4LTgyYTYtMzQ0NDRlYzI1OTUzIiBOYW1lPSJNSUNST1NPRlQgT05FTk9URSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgICA8RXhjZXB0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9ORU5PVEUiIEJpbmFyeU5hbWU9Ik9ORU5PVEUuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT05FTk9URSIgQmluYXJ5TmFtZT0iT05FTk9URU0uRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9FeGNlcHRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIwYmE0YzE1MC0xY2IzLTRjYjktYWIwMi0yNjUyNzQ0MTk0MDkiIE5hbWU9Ik1JQ1JPU09GVCBPVVRMT09LLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9VVExPT0siIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0V4Y2VwdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjY3MTRmODVmLWFkZGEtNGVjNy05NDdjLTc1NTJmN2Q5NDYyNSIgTmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4JDQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI3NGFiMzY4Zi1hMTQ3LTRjZjktODBjMy01M2ZiNjY5YzQ4MzYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSU5GT1BBVEgsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIElORk9QQVRIIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9Ijk3NjlkMjA5LTg1ZjgtNDM4OS1iZGM2LWRkY2EzMGYxYzY3MSIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSBIRUxQIFZJRVdFUiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSEVMUCBWSUVXRVIiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KCTwvUnVsZUNvbGxlY3Rpb24+DQo8L0FwcExvY2tlclBvbGljeT4=", - "id": "4e49ffae-f006-46b9-b74d-785565f7efb4", - "@odata.type": "#microsoft.graph.windowsInformationProtectionAppLockerFile" - } - ], - "exemptAppLockerFiles": [], - "enterpriseNetworkDomainNames": [], - "enterpriseIPRanges": [], - "enterpriseIPRangesAreAuthoritative": false, - "enterpriseProxyServers": [], - "enterpriseInternalProxyServers": [], - "enterpriseProxyServersAreAuthoritative": false, - "neutralDomainResources": [], - "@odata.type": "#microsoft.graph.mdmWindowsInformationProtectionPolicy" + "displayName":"Windows Browser App Protection Worked", + "description":"", + "roleScopeTagIds":["0"], + "version":"\"9c005f0e-0000-0100-0000-65fdb1ab0000\"", + "isAssigned":false, + "deployedAppCount":1, + "printBlocked":true, + "allowedInboundDataTransferSources":"allApps", + "allowedOutboundClipboardSharingLevel":"none", + "allowedOutboundDataTransferDestinations":"none", + "appActionIfUnableToAuthenticateUser":null, + "maximumAllowedDeviceThreatLevel":"notConfigured", + "mobileThreatDefenseRemediationAction":"block", + "minimumRequiredSdkVersion":null, + "minimumWipeSdkVersion":null, + "minimumRequiredOsVersion":null, + "minimumWarningOsVersion":null, + "minimumWipeOsVersion":null, + "minimumRequiredAppVersion":null, + "minimumWarningAppVersion":null, + "minimumWipeAppVersion":null, + "maximumRequiredOsVersion":null, + "maximumWarningOsVersion":null, + "maximumWipeOsVersion":null, + "periodOfflineBeforeWipeIsEnforced":"P90D", + "periodOfflineBeforeAccessCheck":"P1D" } + "@ #################################################### +$BaselineSecurity_Windows = @" +{ + "displayName":"Windows Security Baseline", + "description":"Windows Security Baseline", + "isAssigned":false, + "isMigratingToConfigurationPolicy":null, + "templateId":"034ccd46-190c-4afc-adf1-ad7cc11262eb", + "roleScopeTagIds":["0"] +} +"@ +#> +#################################################### + $windows_endpoint = @" { @@ -3582,33 +3591,38 @@ Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult Write-Host #################################################### + <# -Write-Host "Adding Windows Information Protection Without Enrollment Policy from JSON..." -ForegroundColor Yellow +Write-Host "Adding Windows Baseline Security Policy from JSON..." -ForegroundColor Yellow -$CreateResult = Add-WindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_WIP +$CreateResult = Add-WindowsBaselineSecurity -JSON $BaselineSecurity_Windows write-host "Policy created with id" $CreateResult.id -$WIP_PolicyID = $CreateResult.id +$WinBaselineSec_PolicyID = $CreateResult.id -$Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +# $Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" Write-Host +#> + +#################################################### -Write-Host "Adding Windows Information Protection MDM Policy from JSON..." -ForegroundColor Yellow +Write-Host "Adding Windows Edge Information Protection Without Enrollment Policy from JSON..." -ForegroundColor Yellow -$CreateResult = Add-MDMWindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_WIP_MDM +$CreateResult = Add-WindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_Windows write-host "Policy created with id" $CreateResult.id $WIP_PolicyID = $CreateResult.id -$Assign_Policy = Assign-MDMWindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +$Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" Write-Host -#> + + ############################################################## write-host "Publishing Android" ($MicrosoftAuthenticator | ConvertFrom-Json).displayName -ForegroundColor Yellow From 59e6a840e69f4d1b6dbeb4354d8f8d6e71495441 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:12:28 -0400 Subject: [PATCH 005/107] Add files via upload Checks a list of domain for the number of GPOs and counts the number of folders in each domain folder (confirmation of GPO backup). --- Get-Counts.ps1 | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Get-Counts.ps1 diff --git a/Get-Counts.ps1 b/Get-Counts.ps1 new file mode 100644 index 0000000..aaf9795 --- /dev/null +++ b/Get-Counts.ps1 @@ -0,0 +1,39 @@ +# List of your domains +$domains = get-content -path c:\temp\Domain_List.txt + +# Base directory where domain-named folders are located +$baseDirectory = "C:\temp\Domains" + + +# Initialize an array to hold the result objects +$results = @() + +# Loop through each domain +foreach ($domain in $domains) { + # Count GPOs + $gpoCount = (Get-GPO -All -Domain $domain).Count + + # Construct folder path + $folderPath = Join-Path -Path $baseDirectory -ChildPath $domain + + # Count files in the folder + if (Test-Path -Path $folderPath) { + $items = Get-ChildItem -Path $folderPath -directory -Force + $fileCount = $items.Count + # Diagnostic output + Write-Host "Items in $folderPath" + $items | ForEach-Object { Write-Host "`t$($_.Name)" } + } else { + $fileCount = "Folder not found" + } + + # Add results to the array + $results += [PSCustomObject]@{ + Domain = $domain + GPOCount = $gpoCount + FileCount = $fileCount + } +} + +# Output results to Out-GridView +$results | Out-GridView -Title "Domain, GPO, and Folder Counts" From b26b0d4d7cc06a0560c87c6b31d289341ba30010 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:14:23 -0400 Subject: [PATCH 006/107] Rename Get-DomainComputerTimestamps.ps1 to Get-InstalledSoftware.ps1 --- Get-DomainComputerTimestamps.ps1 => Get-InstalledSoftware.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Get-DomainComputerTimestamps.ps1 => Get-InstalledSoftware.ps1 (82%) diff --git a/Get-DomainComputerTimestamps.ps1 b/Get-InstalledSoftware.ps1 similarity index 82% rename from Get-DomainComputerTimestamps.ps1 rename to Get-InstalledSoftware.ps1 index 6d3197a..2fbd320 100644 --- a/Get-DomainComputerTimestamps.ps1 +++ b/Get-InstalledSoftware.ps1 @@ -1 +1 @@ -Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Select-Object -Property DisplayName, UninstallString | ogv \ No newline at end of file +Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Select-Object -Property DisplayName, UninstallString | ogv From 6094c4203c4b5b9c2b09680053cbc541548701ec Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:15:08 -0400 Subject: [PATCH 007/107] Rename Get-UninstallStrings.ps1 to Get-ComputerTimestamps.ps1 --- Get-UninstallStrings.ps1 => Get-ComputerTimestamps.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Get-UninstallStrings.ps1 => Get-ComputerTimestamps.ps1 (97%) diff --git a/Get-UninstallStrings.ps1 b/Get-ComputerTimestamps.ps1 similarity index 97% rename from Get-UninstallStrings.ps1 rename to Get-ComputerTimestamps.ps1 index 1cec4e2..7c957f4 100644 --- a/Get-UninstallStrings.ps1 +++ b/Get-ComputerTimestamps.ps1 @@ -1 +1 @@ -Get-ADComputer -Filter * -Properties lastlogontimestamp,operatingsystem,operatingsystemversion  | select name,distinguishedname,@{ n = "LastLogonDate"; e = { [datetime]::FromFileTime( $_.lastLogonTimestamp ) } },operatingsystem,operatingsystemversion | ogv \ No newline at end of file +Get-ADComputer -Filter * -Properties lastlogontimestamp,operatingsystem,operatingsystemversion  | select name,distinguishedname,@{ n = "LastLogonDate"; e = { [datetime]::FromFileTime( $_.lastLogonTimestamp ) } },operatingsystem,operatingsystemversion | ogv From 0fcdf16b85f22517d5592c533b0c919f7fc3d840 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 5 Apr 2024 19:33:50 -0400 Subject: [PATCH 008/107] Add files via upload Server 2022 script to setup NDES feature requirements. --- Configure-NDESPrereqs.ps1 | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Configure-NDESPrereqs.ps1 diff --git a/Configure-NDESPrereqs.ps1 b/Configure-NDESPrereqs.ps1 new file mode 100644 index 0000000..5d6dccb --- /dev/null +++ b/Configure-NDESPrereqs.ps1 @@ -0,0 +1,44 @@ +# Function to install a Windows feature +function Install-Feature { + param ( + [string]$featureName + ) + Write-Host "Installing $featureName..." + Install-WindowsFeature -Name $featureName + if ($LASTEXITCODE -eq 0) { + Write-Host "$featureName installed successfully." + } else { + Write-Host "Failed to install $featureName." + } +} + +# Ensure the script is running with administrative privileges +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) +{ + Write-Host "Please run this script as an Administrator!" + exit +} + +# Check if IIS is installed +$iisInstalled = Get-WindowsFeature -Name Web-Server +if (-not $iisInstalled.Installed) { + Install-Feature -featureName "Web-Server" +} else { + Write-Host "IIS is already installed." +} + +# Array of required features +$requiredFeatures = @("Web-Filtering", "NET-Framework-Core", "Web-Asp-Net45", "Web-Mgmt-Compat", "Web-Metabase", "Web-WMI", "NET-Framework-Features", "NET-HTTP-Activation", "NET-WCF-HTTP-Activation45") + +# Check and install each required feature +foreach ($feature in $requiredFeatures) { + $featureStatus = Get-WindowsFeature -Name $feature + if (-not $featureStatus.Installed) { + Write-Host "$feature is not installed." + Install-Feature -featureName $feature + } else { + Write-Host "$feature is already installed." + } +} + +Write-Host "IIS configuration check and installation script completed." From 943b60e63018d68fbed747332d08cdf1bd7ce000 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 5 Apr 2024 19:37:27 -0400 Subject: [PATCH 009/107] Add files via upload Microsoft NDES validation script. --- Validate-NDESConfiguration.ps1 | 1686 ++++++++++++++++++++++++++++++++ 1 file changed, 1686 insertions(+) create mode 100644 Validate-NDESConfiguration.ps1 diff --git a/Validate-NDESConfiguration.ps1 b/Validate-NDESConfiguration.ps1 new file mode 100644 index 0000000..73ac914 --- /dev/null +++ b/Validate-NDESConfiguration.ps1 @@ -0,0 +1,1686 @@ + +<# + +.SYNOPSIS +Highlights configuration problems on an NDES server, as configured for use with Intune Standalone SCEP certificates. + +.DESCRIPTION +Validate-NDESConfig looks at the configuration of your NDES server and ensures it aligns to the "Configure and manage SCEP +certificates with Intune" article. + +.NOTE This script is used purely to validate the configuration. All remedial tasks will need to be carried out manually. +Where possible, a link and section description will be provided. + +.EXAMPLE +.\Validate-NDESConfiguration -NDESServiceAccount Contoso\NDES_SVC.com -IssuingCAServerFQDN IssuingCA.contoso.com -SCEPUserCertTemplate SCEPGeneral + +.EXAMPLE +.\Validate-NDESConfiguration -help + +.LINK +https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure + +#> + +[CmdletBinding(DefaultParameterSetName="NormalRun")] + +Param( +[parameter(Mandatory=$true,ParameterSetName="NormalRun")] +[alias("sa")] +[ValidateScript({ + if ($_ -match ".\\."){ + + $True + + } + + else { + + Throw "Please use the format Domain\Username for the NDES Service Account variable." + + } + + $EnteredDomain = $_.split("\") + $ads = New-Object -ComObject ADSystemInfo + $Domain = $ads.GetType().InvokeMember('DomainShortName','GetProperty', $Null, $ads, $Null) + + if ($EnteredDomain -like "$Domain") { + + $True + + } + + else { + + Throw "Incorrect Domain. Ensure domain is '$($Domain)\'" + + } + + } +)] +[string]$NDESServiceAccount, + +[parameter(Mandatory=$true,ParameterSetName="NormalRun")] +[alias("ca")] +[ValidateScript({ + $Domain = (Get-WmiObject Win32_ComputerSystem).domain + if ($_ -match $Domain) { + + $True + + } + + else { + + Throw "The Network Device Enrollment Server and the Certificate Authority are not members of the same Active Directory domain. This is an unsupported configuration." + + } + + } +)] +[string]$IssuingCAServerFQDN, + +[parameter(Mandatory=$true,ParameterSetName="NormalRun")] +[alias("t")] +[string]$SCEPUserCertTemplate, + +[parameter(ParameterSetName="Help")] +[alias("h","?","/?")] +[switch]$help, + +[parameter(ParameterSetName="Help")] +[alias("u")] +[switch]$usage + + +) + +####################################################################### + +Function Log-ScriptEvent { + +[CmdletBinding()] + +Param( + [parameter(Mandatory=$True)] + [String]$LogFilePath, + + [parameter(Mandatory=$True)] + [String]$Value, + + [parameter(Mandatory=$True)] + [String]$Component, + + [parameter(Mandatory=$True)] + [ValidateRange(1,3)] + [Single]$Severity + ) + +$DateTime = New-Object -ComObject WbemScripting.SWbemDateTime +$DateTime.SetVarDate($(Get-Date)) +$UtcValue = $DateTime.Value +$UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21) + +$LogLine = "" +` + "" + +Add-Content -Path $LogFilePath -Value $LogLine + +} + +########################################################################################################## + +function Show-Usage { + + Write-Host + Write-Host "-help -h Displays the help." + Write-Host "-usage -u Displays this usage information." + Write-Host "-NDESExternalHostname -ed External DNS name for the NDES server (SSL certificate subject will be checked for this. It should be in the SAN of the certificate if" + write-host " clients communicate directly with the NDES server)" + Write-Host "-NDESServiceAccount -sa Username of the NDES service account. Format is Domain\sAMAccountName, such as Contoso\NDES_SVC." + Write-Host "-IssuingCAServerFQDN -ca Name of the issuing CA to which you'll be connecting the NDES server. Format is FQDN, such as 'MyIssuingCAServer.contoso.com'." + Write-Host "-SCEPUserCertTemplate -t Name of the SCEP Certificate template. Please note this is _not_ the display name of the template. Value should not contain spaces." + Write-Host + +} + +####################################################################### + +function Get-NDESHelp { + + Write-Host + Write-Host "Verifies if the NDES server meets all the required configuration. " + Write-Host + Write-Host "The NDES server role is required as back-end infrastructure for Intune Standalone for delivering VPN and Wi-Fi certificates via the SCEP protocol to mobile devices and desktop clients." + Write-Host "See https://docs.microsoft.com/en-us/intune/certificates-scep-configure." + Write-Host + +} + +####################################################################### + + if ($help){ + + Get-NDESHelp + break + + } + + if ($usage){ + + Show-Usage + break + } + +####################################################################### + +#Requires -version 3.0 +#Requires -RunAsAdministrator + +####################################################################### + +$parent = [System.IO.Path]::GetTempPath() +[string] $name = [System.Guid]::NewGuid() +New-Item -ItemType Directory -Path (Join-Path $parent $name) | Out-Null +$TempDirPath = "$parent$name" +$LogFilePath = "$($TempDirPath)\Validate-NDESConfig.log" + +####################################################################### + +#region Proceed with Variables... + + Write-Host + Write-host "......................................................." + Write-Host + Write-Host "NDES Service Account = "-NoNewline + Write-Host "$($NDESServiceAccount)" -ForegroundColor Cyan + Write-host + Write-Host "Issuing CA Server = " -NoNewline + Write-Host "$($IssuingCAServerFQDN)" -ForegroundColor Cyan + Write-host + Write-Host "SCEP Certificate Template = " -NoNewline + Write-Host "$($SCEPUserCertTemplate)" -ForegroundColor Cyan + Write-Host + Write-host "......................................................." + Write-Host + Write-Host "Proceed with variables? [Y]es, [N]o" + + $confirmation = Read-Host + +#endregion + +####################################################################### + + if ($confirmation -eq 'y'){ + Write-Host + Write-host "......................................................." + Log-ScriptEvent $LogFilePath "Initializing log file $($TempDirPath)\Validate-NDESConfig.log" NDES_Validation 1 + Log-ScriptEvent $LogFilePath "Proceeding with variables=YES" NDES_Validation 1 + Log-ScriptEvent $LogFilePath "NDESServiceAccount=$($NDESServiceAccount)" NDES_Validation 1 + Log-ScriptEvent $LogFilePath "IssuingCAServer=$($IssuingCAServerFQDN)" NDES_Validation 1 + Log-ScriptEvent $LogFilePath "SCEPCertificateTemplate=$($SCEPUserCertTemplate)" NDES_Validation 1 + +####################################################################### + +#region Install RSAT tools, Check if NDES and IIS installed + + if (-not (Get-WindowsFeature ADCS-Device-Enrollment).Installed){ + + Write-Host "Error: NDES Not installed" -BackgroundColor Red + write-host "Exiting....................." + Log-ScriptEvent $LogFilePath "NDES Not installed" NDES_Validation 3 + break + + } + +Install-WindowsFeature RSAT-AD-PowerShell | Out-Null + +Import-Module ActiveDirectory | Out-Null + + if (-not (Get-WindowsFeature Web-WebServer).Installed){ + + $IISNotInstalled = $TRUE + Write-Warning "IIS is not installed. Some tests will not run as we're unable to import the WebAdministration module" + Write-Host + Log-ScriptEvent $LogFilePath "IIS is not installed. Some tests will not run as we're unable to import the WebAdministration module" NDES_Validation 2 + + } + + else { + + Import-Module WebAdministration | Out-Null + + } + +#endregion + +####################################################################### + +#region checking OS version + + Write-Host + Write-host "Checking Windows OS version..." -ForegroundColor Yellow + Write-host + Log-ScriptEvent $LogFilePath "Checking OS Version" NDES_Validation 1 + +$OSVersion = (Get-CimInstance -class Win32_OperatingSystem).Version +$MinOSVersion = "6.3" + + if ([version]$OSVersion -lt [version]$MinOSVersion){ + + Write-host "Error: Unsupported OS Version. NDES Requires 2012 R2 and above." -BackgroundColor Red + Log-ScriptEvent $LogFilePath "Unsupported OS Version. NDES Requires 2012 R2 and above." NDES_Validation 3 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "OS Version " -NoNewline + write-host "$($OSVersion)" -NoNewline -ForegroundColor Cyan + write-host " supported." + Log-ScriptEvent $LogFilePath "Server is version $($OSVersion)" NDES_Validation 1 + + } + +#endregion + +####################################################################### + +#region Checking NDES Service Account properties in Active Directory + +Write-host +Write-host "......................................................." +Write-Host +Write-host "Checking NDES Service Account properties in Active Directory..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking NDES Service Account properties in Active Directory" NDES_Validation 1 + +$ADUser = $NDESServiceAccount.split("\")[1] + +$ADUserProps = (Get-ADUser $ADUser -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut) + + if ($ADUserProps.enabled -ne $TRUE -OR $ADUserProps.PasswordExpired -ne $false -OR $ADUserProps.LockedOut -eq $TRUE){ + + Write-Host "Error: Problem with the AD account. Please see output below to determine the issue" -BackgroundColor Red + Write-Host + Log-ScriptEvent $LogFilePath "Problem with the AD account. Please see output below to determine the issue" NDES_Validation 3 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "NDES Service Account seems to be in working order:" + Log-ScriptEvent $LogFilePath "NDES Service Account seems to be in working order" NDES_Validation 1 + + } + + + +Get-ADUser $ADUser -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut | fl SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut + +#endregion + +####################################################################### + +#region Checking if NDES server is the CA + +Write-host "`n.......................................................`n" +Write-host "Checking if NDES server is the CA...`n" -ForegroundColor Yellow +Log-ScriptEvent $LogFilePath "Checking if NDES server is the CA" NDES_Validation 1 + +$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname +$CARoleInstalled = (Get-WindowsFeature ADCS-Cert-Authority).InstallState -eq "Installed" + + if ($hostname -match $IssuingCAServerFQDN){ + + Write-host "Error: NDES is running on the CA. This is an unsupported configuration!" -BackgroundColor Red + Log-ScriptEvent $LogFilePath "NDES is running on the CA" NDES_Validation 3 + + } + elseif($CARoleInstalled) + { + Write-host "Error: NDES server has Certification Authority Role installed. This is an unsupported configuration!" -BackgroundColor Red + Log-ScriptEvent $LogFilePath "NDES server has Certification Authority Role installed" NDES_Validation 3 + } + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "NDES server is not running on the CA" + Log-ScriptEvent $LogFilePath "NDES server is not running on the CA" NDES_Validation 1 + + } + +#endregion + +####################################################################### + +#region Checking NDES Service Account local permissions + +Write-host +Write-host "......................................................." +Write-host +Write-host "Checking NDES Service Account local permissions..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking NDES Service Account local permissions" NDES_Validation 1 + + if ((net localgroup) -match "Administrators"){ + + $LocalAdminsMember = ((net localgroup Administrators)) + + if ($LocalAdminsMember -like "*$NDESServiceAccount*"){ + + Write-Warning "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use IIS_IUSERS instead." + Log-ScriptEvent $LogFilePath "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use IIS_IUSERS instead." NDES_Validation 2 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "NDES Service account is not a member of the Local Administrators group" + Log-ScriptEvent $LogFilePath "NDES Service account is not a member of the Local Administrators group" NDES_Validation 1 + + } + + Write-host + Write-Host "Checking NDES Service account is a member of the IIS_IUSR group..." -ForegroundColor Yellow + Write-host + + if ((net localgroup) -match "IIS_IUSRS"){ + + $IIS_IUSRMembers = ((net localgroup IIS_IUSRS)) + + if ($IIS_IUSRMembers -like "*$NDESServiceAccount*"){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "NDES Service Account is a member of the local IIS_IUSR group" -NoNewline + Log-ScriptEvent $LogFilePath "NDES Service Account is a member of the local IIS_IUSR group" NDES_Validation 1 + + } + + else { + + Write-Host "Error: NDES Service Account is not a member of the local IIS_IUSR group" -BackgroundColor red + Log-ScriptEvent $LogFilePath "NDES Service Account is not a member of the local IIS_IUSR group" NDES_Validation 3 + + Write-host + Write-host "Checking Local Security Policy for explicit rights via gpedit..." -ForegroundColor Yellow + Write-Host + $TempFile = [System.IO.Path]::GetTempFileName() + & "secedit" "/export" "/cfg" "$TempFile" | Out-Null + $LocalSecPol = Get-Content $TempFile + $ADUserProps = Get-ADUser $ADUser + $NDESSVCAccountSID = $ADUserProps.SID.Value + $LocalSecPolResults = $LocalSecPol | Select-String $NDESSVCAccountSID + + if ($LocalSecPolResults -match "SeInteractiveLogonRight" -AND $LocalSecPolResults -match "SeBatchLogonRight" -AND $LocalSecPolResults -match "SeServiceLogonRight"){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "NDES Service Account has been assigned the Logon Locally, Logon as a Service and Logon as a batch job rights explicitly." + Log-ScriptEvent $LogFilePath "NDES Service Account has been assigned the Logon Locally, Logon as a Service and Logon as a batch job rights explicitly." NDES_Validation 1 + Write-Host + Write-Host "Note:" -BackgroundColor Red -NoNewline + Write-Host " The Logon Locally is not required in normal runtime." + Write-Host + Write-Host "Note:" -BackgroundColor Red -NoNewline + Write-Host 'Consider using the IIS_IUSERS group instead of explicit rights as documented under "Step 1 - Create an NDES service account".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + + } + + else { + + Write-Host "Error: NDES Service Account has _NOT_ been assigned the Logon Locally, Logon as a Service or Logon as a batch job rights _explicitly_." -BackgroundColor red + Write-Host 'Please review "Step 1 - Create an NDES service account".' + write-host "https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "NDES Service Account has _NOT_ been assigned the Logon Locally, Logon as a Service or Logon as a batch job rights _explicitly_." NDES_Validation 3 + + } + + } + + } + + else { + + Write-Host "Error: No IIS_IUSRS group exists. Ensure IIS is installed." -BackgroundColor red + write-host 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' + write-host "https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "No IIS_IUSRS group exists. Ensure IIS is installed." NDES_Validation 3 + + } + + } + + else { + + Write-Warning "No local Administrators group exists, likely due to this being a Domain Controller. It is not recommended to run NDES on a Domain Controller." + Log-ScriptEvent $LogFilePath "No local Administrators group exists, likely due to this being a Domain Controller. It is not recommended to run NDES on a Domain Controller." NDES_Validation 2 + + } + +#endregion + +####################################################################### + +#region Checking Windows Features are installed. + +Write-host +Write-Host +Write-host "......................................................." +Write-host +Write-host "Checking Windows Features are installed..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Windows Features are installed..." NDES_Validation 1 + +$WindowsFeatures = @("Web-Filtering","Web-Net-Ext45","NET-Framework-45-Core","NET-WCF-HTTP-Activation45","Web-Metabase","Web-WMI") + +foreach($WindowsFeature in $WindowsFeatures){ + +$Feature = Get-WindowsFeature $WindowsFeature +$FeatureDisplayName = $Feature.displayName + + if($Feature.installed){ + + Write-host "Success:" -ForegroundColor Green -NoNewline + write-host "$FeatureDisplayName Feature Installed" + Log-ScriptEvent $LogFilePath "$($FeatureDisplayName) Feature Installed" NDES_Validation 1 + + } + + else { + + Write-Host "Error: $FeatureDisplayName Feature not installed!" -BackgroundColor red + Write-Host 'Please review "Step 3.1b - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "$($FeatureDisplayName) Feature not installed" NDES_Validation 3 + + } + +} + +#endregion + +################################################################# + +#region Checking NDES Install Paramaters + +$ErrorActionPreference = "SilentlyContinue" + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking NDES Install Paramaters..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking NDES Install Paramaters" NDES_Validation 1 + +$InstallParams = @(Get-WinEvent -LogName "Microsoft-Windows-CertificateServices-Deployment/Operational" | Where-Object {$_.id -eq "105"}| +Where-Object {$_.message -match "Install-AdcsNetworkDeviceEnrollmentService"}| Sort-Object -Property TimeCreated -Descending | Select-Object -First 1) + + if ($InstallParams.Message -match '-SigningProviderName "Microsoft Strong Cryptographic Provider"' -AND ($InstallParams.Message -match '-EncryptionProviderName "Microsoft Strong Cryptographic Provider"')) { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "Correct CSP used in install parameters" + Write-host + Write-Host $InstallParams.Message + Log-ScriptEvent $LogFilePath "Correct CSP used in install parameters:" NDES_Validation 1 + Log-ScriptEvent $LogFilePath "$($InstallParams.Message)" NDES_Eventvwr 1 + + } + + else { + + Write-Host "Error: Incorrect CSP selected during install. NDES only supports the CryptoAPI CSP." -BackgroundColor red + Write-Host + Write-Host $InstallParams.Message + Log-ScriptEvent $LogFilePath "Error: Incorrect CSP selected during install. NDES only supports the CryptoAPI CSP" NDES_Validation 3 + Log-ScriptEvent $LogFilePath "$($InstallParams.Message)" NDES_Eventvwr 3 + } + +$ErrorActionPreference = "Continue" + +#endregion + +################################################################# + +#region Checking IIS Application Pool health + +Write-host +Write-host "......................................................." +Write-host +Write-host "Checking IIS Application Pool health..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking IIS Application Pool health" NDES_Validation 1 + + if (-not ($IISNotInstalled -eq $TRUE)){ + + # If SCEP AppPool Exists + if (Test-Path 'IIS:\AppPools\SCEP'){ + + $IISSCEPAppPoolAccount = Get-Item 'IIS:\AppPools\SCEP' | select -expandproperty processmodel | select -Expand username + + if ((Get-WebAppPoolState "SCEP").value -match "Started"){ + + $SCEPAppPoolRunning = $TRUE + + } + + } + + else { + + Write-Host "Error: SCEP Application Pool missing!" -BackgroundColor red + Write-Host 'Please review "Step 3.1 - Configure prerequisites on the NDES server"'. + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "SCEP Application Pool missing" NDES_Validation 3 + + } + + if ($IISSCEPAppPoolAccount -contains "$NDESServiceAccount"){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "Application Pool is configured to use " -NoNewline + Write-Host "$($IISSCEPAppPoolAccount)" + Log-ScriptEvent $LogFilePath "Application Pool is configured to use $($IISSCEPAppPoolAccount)" NDES_Validation 1 + + } + + else { + + Write-Host "Error: Application Pool is not configured to use the NDES Service Account" -BackgroundColor red + Write-Host 'Please review "Step 4.1 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "Application Pool is not configured to use the NDES Service Account" NDES_Validation 3 + + } + + if ($SCEPAppPoolRunning){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "SCEP Application Pool is Started " -NoNewline + Log-ScriptEvent $LogFilePath "SCEP Application Pool is Started" NDES_Validation 1 + + } + + else { + + Write-Host "Error: SCEP Application Pool is stopped!" -BackgroundColor red + Write-Host "Please start the SCEP Application Pool via IIS Management Console. You should also review the Application Event log output for Errors" + Log-ScriptEvent $LogFilePath "SCEP Application Pool is stopped" NDES_Validation 3 + + } + + } + + else { + + Write-Host "IIS is not installed." -BackgroundColor red + Log-ScriptEvent $LogFilePath "SCEP Application Pool is stopped" NDES_Validation 3 + + } + +#endregion + +################################################################# + +#region Checking Request Filtering + +Write-Host +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Request Filtering (Default Web Site -> Request Filtering -> Edit Feature Setting) has been configured in IIS..." -ForegroundColor Yellow +Write-Host +Log-ScriptEvent $LogFilePath "Checking Request Filtering" NDES_Validation 1 + + if (-not ($IISNotInstalled -eq $TRUE)){ + + [xml]$RequestFiltering = (c:\windows\system32\inetsrv\appcmd.exe list config "default web site" /section:requestfiltering) + + if ($RequestFiltering.'system.webserver'.security.requestFiltering.requestLimits.maxQueryString -eq "65534"){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "MaxQueryString Set Correctly" + Log-ScriptEvent $LogFilePath "MaxQueryString Set Correctly" NDES_Validation 1 + + } + + else { + + Write-Host "MaxQueryString not set correctly!" -BackgroundColor red + Write-Host 'Please review "Step 4.4 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "MaxQueryString not set correctly" NDES_Validation 3 + + } + + if ($RequestFiltering.'system.webserver'.security.requestFiltering.requestLimits.maxUrl -eq "65534"){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "MaxUrl Set Correctly" + Log-ScriptEvent $LogFilePath "MaxUrl Set Correctly" NDES_Validation 1 + + } + + else { + + Write-Host "maxUrl not set correctly!" -BackgroundColor red + Write-Host 'Please review "Step 4.4 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure'" + Log-ScriptEvent $LogFilePath "maxUrl not set correctly" NDES_Validation 3 + + } + + } + + else { + + Write-Host "IIS is not installed." -BackgroundColor red + Log-ScriptEvent $LogFilePath "IIS is not installed" NDES_Validation 3 + + } + +#endregion + +################################################################# + +#region Checking registry has been set to allow long URLs + +Write-host +Write-host "......................................................." +Write-host +Write-Host 'Checking registry "HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters" has been set to allow long URLs...' -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking registry (HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters) has been set to allow long URLs" NDES_Validation 1 + + if (-not ($IISNotInstalled -eq $TRUE)){ + + If ((Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxFieldLength).MaxfieldLength -notmatch "65534"){ + + Write-Host "Error: MaxFieldLength not set to 65534 in the registry!" -BackgroundColor red + Write-Host + Write-Host 'Please review "Step 4.3 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "MaxFieldLength not set to 65534 in the registry" NDES_Validation 3 + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "MaxFieldLength set correctly" + Log-ScriptEvent $LogFilePath "MaxFieldLength set correctly" NDES_Validation 1 + + } + + if ((Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxRequestBytes).MaxRequestBytes -notmatch "65534"){ + + Write-Host "MaxRequestBytes not set to 65534 in the registry!" -BackgroundColor red + Write-Host + Write-Host 'Please review "Step 4.3 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure'" + Log-ScriptEvent $LogFilePath "MaxRequestBytes not set to 65534 in the registry" NDES_Validation 3 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "MaxRequestBytes set correctly" + Log-ScriptEvent $LogFilePath "MaxRequestBytes set correctly" NDES_Validation 1 + + } + + } + + else { + + Write-Host "IIS is not installed." -BackgroundColor red + Log-ScriptEvent $LogFilePath "IIS is not installed." NDES_Validation 3 + + } + +#endregion + +################################################################# + +#region Checking SPN has been set... + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking SPN has been set..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking SPN has been set" NDES_Validation 1 + +$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname + +$spn = setspn.exe -L $ADUser + + if ($spn -match $hostname){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "Correct SPN set for the NDES service account:" + Write-host + Write-Host $spn -ForegroundColor Cyan + Log-ScriptEvent $LogFilePath "Correct SPN set for the NDES service account: $($spn)" NDES_Validation 1 + + } + + else { + + Write-Host "Error: Missing or Incorrect SPN set for the NDES Service Account!" -BackgroundColor red + Write-Host 'Please review "Step 3.1c - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "Missing or Incorrect SPN set for the NDES Service Account" NDES_Validation 3 + + } + +#endregion + +################################################################# + +#region Checking there are no intermediate certs are in the Trusted Root store + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking there are no intermediate certs are in the Trusted Root store..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking there are no intermediate certs are in the Trusted Root store" NDES_Validation 1 + +$IntermediateCertCheck = Get-Childitem cert:\LocalMachine\root -Recurse | Where-Object {$_.Issuer -ne $_.Subject} + + if ($IntermediateCertCheck){ + + Write-Host "Error: Intermediate certificate found in the Trusted Root store. This can cause undesired effects and should be removed." -BackgroundColor red + Write-Host "Certificates:" + Write-Host + Write-Host $IntermediateCertCheck + Log-ScriptEvent $LogFilePath "Intermediate certificate found in the Trusted Root store: $($IntermediateCertCheck)" NDES_Validation 3 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "Trusted Root store does not contain any Intermediate certificates." + Log-ScriptEvent $LogFilePath "Trusted Root store does not contain any Intermediate certificates." NDES_Validation 1 + + } + +#endregion + +################################################################# + +#region Checking the EnrollmentAgentOffline and CEPEncryption are present + +$ErrorActionPreference = "Silentlycontinue" + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking the EnrollmentAgentOffline and CEPEncryption are present..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking the EnrollmentAgentOffline and CEPEncryption are present" NDES_Validation 1 + +$certs = Get-ChildItem cert:\LocalMachine\My\ + + # Looping through all certificates in LocalMachine Store + Foreach ($item in $certs){ + + $Output = ($item.Extensions| where-object {$_.oid.FriendlyName -like "**"}).format(0).split(",") + + if ($Output -match "EnrollmentAgentOffline"){ + + $EnrollmentAgentOffline = $TRUE + + } + + if ($Output -match "CEPEncryption"){ + + $CEPEncryption = $TRUE + + } + + } + + # Checking if EnrollmentAgentOffline certificate is present + if ($EnrollmentAgentOffline){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "EnrollmentAgentOffline certificate is present" + Log-ScriptEvent $LogFilePath "EnrollmentAgentOffline certificate is present" NDES_Validation 1 + + } + + else { + + Write-Host "Error: EnrollmentAgentOffline certificate is not present!" -BackgroundColor red + Write-Host "This can take place when an account without Enterprise Admin permissions installs NDES. You may need to remove the NDES role and reinstall with the correct permissions." + write-host 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "EnrollmentAgentOffline certificate is not present" NDES_Validation 3 + + } + + # Checking if CEPEncryption is present + if ($CEPEncryption){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "CEPEncryption certificate is present" + Log-ScriptEvent $LogFilePath "CEPEncryption certificate is present" NDES_Validation 1 + + } + + else { + + Write-Host "Error: CEPEncryption certificate is not present!" -BackgroundColor red + Write-Host "This can take place when an account without Enterprise Admin permissions installs NDES. You may need to remove the NDES role and reinstall with the correct permissions." + write-host 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "CEPEncryption certificate is not present" NDES_Validation 3 + + } + +$ErrorActionPreference = "Continue" + +#endregion + +################################################################# + +#region Checking registry has been set with the SCEP certificate template name + +Write-host +Write-host "......................................................." +Write-host +Write-Host 'Checking registry "HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP" has been set with the SCEP certificate template name...' -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking registry (HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP) has been set with the SCEP certificate template name" NDES_Validation 1 + + if (-not (Test-Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP)){ + + Write-host "Error: Registry key does not exist. This can occur if the NDES role has been installed but not configured." -BackgroundColor Red + Write-host 'Please review "Step 3 - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "MSCEP Registry key does not exist." NDES_Validation 3 + + } + + else { + + $SignatureTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name SignatureTemplate).SignatureTemplate + $EncryptionTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name EncryptionTemplate).EncryptionTemplate + $GeneralPurposeTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name GeneralPurposeTemplate).GeneralPurposeTemplate + $DefaultUsageTemplate = "IPSECIntermediateOffline" + + if ($SignatureTemplate -match $DefaultUsageTemplate -AND $EncryptionTemplate -match $DefaultUsageTemplate -AND $GeneralPurposeTemplate -match $DefaultUsageTemplate){ + + Write-Host "Error: Registry has not been configured with the SCEP Certificate template name. Default values have _not_ been changed." -BackgroundColor red + write-host 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Write-Host + Log-ScriptEvent $LogFilePath "Registry has not been configured with the SCEP Certificate template name. Default values have _not_ been changed." NDES_Validation 3 + $FurtherReading = $FALSE + + } + + else { + + Write-Host "One or more default values have been changed." + Write-Host + write-host "Checking SignatureTemplate key..." + Write-host + + if ($SignatureTemplate -match $SCEPUserCertTemplate){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _SignatureTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template." + Write-host + Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate)' has been written to the registry under the _SignatureTemplate_ key" NDES_Validation 1 + + } + + else { + + Write-Warning '"SignatureTemplate key does not match the SCEP certificate template name. Unless your template is explicitly set for the "Signature" purpose, this can safely be ignored."' + Write-Host + write-host "Registry value: " -NoNewline + Write-host "$($SignatureTemplate)" -ForegroundColor Cyan + Write-Host + write-host "SCEP certificate template value: " -NoNewline + Write-host "$($SCEPUserCertTemplate)" -ForegroundColor Cyan + Write-Host + Log-ScriptEvent $LogFilePath "SignatureTemplate key does not match the SCEP certificate template name.Registry value=$($SignatureTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 + + } + + Write-host "......................." + Write-Host + Write-Host "Checking EncryptionTemplate key..." + Write-host + + if ($EncryptionTemplate -match $SCEPUserCertTemplate){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _EncryptionTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template." + Write-host + Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate) has been written to the registry under the _EncryptionTemplate_ key" NDES_Validation 1 + + + } + + else { + + Write-Warning '"EncryptionTemplate key does not match the SCEP certificate template name. Unless your template is explicitly set for the "Encryption" purpose, this can safely be ignored."' + Write-Host + write-host "Registry value: " -NoNewline + Write-host "$($EncryptionTemplate)" -ForegroundColor Cyan + Write-Host + write-host "SCEP certificate template value: " -NoNewline + Write-host "$($SCEPUserCertTemplate)" -ForegroundColor Cyan + Write-Host + Log-ScriptEvent $LogFilePath "EncryptionTemplate key does not match the SCEP certificate template name.Registry value=$($EncryptionTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 + + + } + + Write-host "......................." + Write-Host + Write-Host "Checking GeneralPurposeTemplate key..." + Write-host + + if ($GeneralPurposeTemplate -match $SCEPUserCertTemplate){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _GeneralPurposeTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template" + Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate) has been written to the registry under the _GeneralPurposeTemplate_ key" NDES_Validation 1 + + } + + else { + + Write-Warning '"GeneralPurposeTemplate key does not match the SCEP certificate template name. Unless your template is set for the "Signature and Encryption" (General) purpose, this can safely be ignored."' + Write-Host + write-host "Registry value: " -NoNewline + Write-host "$($GeneralPurposeTemplate)" -ForegroundColor Cyan + Write-Host + write-host "SCEP certificate template value: " -NoNewline + Write-host "$($SCEPUserCertTemplate)" -ForegroundColor Cyan + Write-Host + Log-ScriptEvent $LogFilePath "GeneralPurposeTemplate key does not match the SCEP certificate template name.Registry value=$($GeneralPurposeTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 + + + } + + } + + if ($furtherreading-EQ $true){ + + Write-host "......................." + Write-Host + Write-host 'For further reading, please review "Step 4.2 - Configure NDES for use with Intune".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + + } + + } + +$ErrorActionPreference = "Continue" + +#endregion + +################################################################# + +#region Checking server certificate. + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking IIS SSL certificate is valid for use..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking IIS SSL certificate is valid for use" NDES_Validation 1 + +$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname +$serverAuthEKU = "1.3.6.1.5.5.7.3.1" # Server Authentication +$allSSLCerts = Get-ChildItem Cert:\LocalMachine\My +$BoundServerCert = netsh http show sslcert + + foreach ($Cert in $allSSLCerts) { + + $ServerCertThumb = $cert.Thumbprint + + if ($BoundServerCert -match $ServerCertThumb){ + + $BoundServerCertThumb = $ServerCertThumb + + } + + } + +$ServerCertObject = Get-ChildItem Cert:\LocalMachine\My\$BoundServerCertThumb + + if ($ServerCertObject.Issuer -match $ServerCertObject.Subject){ + + $SelfSigned = $true + + } + + else { + + $SelfSigned = $false + + } + + if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU -AND (($ServerCertObject.Subject -match $hostname) -or ($ServerCertObject.DnsNameList -match $hostname)) -AND $ServerCertObject.Issuer -notmatch $ServerCertObject.Subject){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "Certificate bound in IIS is valid:" + Write-Host + Write-Host "Subject: " -NoNewline + Write-host "$($ServerCertObject.Subject)" -ForegroundColor Cyan + Write-Host + Write-Host "Thumbprint: " -NoNewline + Write-Host "$($ServerCertObject.Thumbprint)" -ForegroundColor Cyan + Write-Host + Write-Host "Valid Until: " -NoNewline + Write-Host "$($ServerCertObject.NotAfter)" -ForegroundColor Cyan + Write-Host + Write-Host "If this NDES server is in your perimeter network, please ensure the external hostname is shown below:" -ForegroundColor Blue -BackgroundColor White + $DNSNameList = $ServerCertObject.DNSNameList.unicode + Write-Host + write-host "Internal and External hostnames: " -NoNewline + Write-host "$($DNSNameList)" -ForegroundColor Cyan + Log-ScriptEvent $LogFilePath "Certificate bound in IIS is valid. Subject:$($ServerCertObject.Subject)|Thumbprint:$($ServerCertObject.Thumbprint)|ValidUntil:$($ServerCertObject.NotAfter)|Internal&ExternalHostnames:$($DNSNameList)" NDES_Validation 1 + + } + + else { + + Write-Host "Error: The certificate bound in IIS is not valid for use. Reason:" -BackgroundColor red + write-host + + + if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU) { + + $EKUValid = $true + + } + + else { + + $EKUValid = $false + + write-host "Correct EKU: " -NoNewline + Write-Host "$($EKUValid)" -ForegroundColor Cyan + Write-Host + + } + + if ($ServerCertObject.Subject -match $hostname) { + + $SubjectValid = $true + + } + + else { + + $SubjectValid = $false + + write-host "Correct Subject: " -NoNewline + write-host "$($SubjectValid)" -ForegroundColor Cyan + Write-Host + + } + + if ($SelfSigned -eq $false){ + + Out-Null + + } + + else { + + write-host "Is Self-Signed: " -NoNewline + write-host "$($SelfSigned)" -ForegroundColor Cyan + Write-Host + + } + + Write-Host 'Please review "Step 4 - Configure NDES for use with Intune>To Install and bind certificates on the NDES Server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "The certificate bound in IIS is not valid for use. CorrectEKU=$($EKUValid)|CorrectSubject=$($SubjectValid)|IsSelfSigned=$($SelfSigned)" NDES_Validation 3 + +} + +#endregion + +################################################################# + +#region Checking Client certificate. + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Client certificate (NDES Policy module) is valid for use..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Client certificate (NDES Policy module) is valid for use" NDES_Validation 1 + +$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname +$clientAuthEku = "1.3.6.1.5.5.7.3.2" # Client Authentication +$NDESCertThumbprint = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\Modules\NDESPolicy -Name NDESCertThumbprint).NDESCertThumbprint +$ClientCertObject = Get-ChildItem Cert:\LocalMachine\My\$NDESCertThumbprint + + if ($ClientCertObject.Issuer -match $ClientCertObject.Subject){ + + $ClientCertSelfSigned = $true + + } + + else { + + $ClientCertSelfSigned = $false + + } + + if ($ClientCertObject.EnhancedKeyUsageList -match $clientAuthEku -AND $ClientCertObject.Subject -match $hostname -AND $ClientCertObject.Issuer -notmatch $ClientCertObject.Subject){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "Client certificate bound to NDES Connector is valid:" + Write-Host + Write-Host "Subject: " -NoNewline + Write-host "$($ClientCertObject.Subject)" -ForegroundColor Cyan + Write-Host + Write-Host "Thumbprint: " -NoNewline + Write-Host "$($ClientCertObject.Thumbprint)" -ForegroundColor Cyan + Write-Host + Write-Host "Valid Until: " -NoNewline + Write-Host "$($ClientCertObject.NotAfter)" -ForegroundColor Cyan + Log-ScriptEvent $LogFilePath "Client certificate bound to NDES Connector is valid. Subject:$($ClientCertObject.Subject)|Thumbprint:$($ClientCertObject.Thumbprint)|ValidUntil:$($ClientCertObject.NotAfter)" NDES_Validation 1 + + } + + else { + + Write-Host "Error: The certificate bound to the NDES Connector is not valid for use. Reason:" -BackgroundColor red + write-host + + if ($ClientCertObject.EnhancedKeyUsageList -match $clientAuthEku) { + + $ClientCertEKUValid = $true + + } + + else { + + $ClientCertEKUValid = $false + + write-host "Correct EKU: " -NoNewline + Write-Host "$($ClientCertEKUValid)" -ForegroundColor Cyan + Write-Host + + } + + if ($ClientCertObject.Subject -match $hostname) { + + $ClientCertSubjectValid = $true + + } + + else { + + $ClientCertSubjectValid = $false + + write-host "Correct Subject: " -NoNewline + write-host "$($ClientCertSubjectValid)" -ForegroundColor Cyan + Write-Host + + } + + if ($ClientCertSelfSigned -eq $false){ + + Out-Null + + } + + else { + + write-host "Is Self-Signed: " -NoNewline + write-host "$($ClientCertSelfSigned)" -ForegroundColor Cyan + Write-Host + + } + + Write-Host 'Please review "Step 4 - Configure NDES for use with Intune>To Install and bind certificates on the NDES Server".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Log-ScriptEvent $LogFilePath "The certificate bound to the NDES Connector is not valid for use. CorrectEKU=$($ClientCertEKUValid)|CorrectSubject=$($ClientCertSubjectValid)|IsSelfSigned=$($ClientCertSelfSigned)" NDES_Validation 3 + + +} + +#endregion + +################################################################# + +#region Checking behaviour of internal NDES URL + +Write-host +Write-host "......................................................." +$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname +Write-host +Write-Host "Checking behaviour of internal NDES URL: " -NoNewline -ForegroundColor Yellow +Write-Host "https://$hostname/certsrv/mscep/mscep.dll" -ForegroundColor Cyan +Write-host +Log-ScriptEvent $LogFilePath "Checking behaviour of internal NDES URL" NDES_Validation 1 +Log-ScriptEvent $LogFilePath "Https://$hostname/certsrv/mscep/mscep.dll" NDES_Validation 1 + +$Statuscode = try {(Invoke-WebRequest -Uri https://$hostname/certsrv/mscep/mscep.dll).statuscode} catch {$_.Exception.Response.StatusCode.Value__} + + if ($statuscode -eq "200"){ + + Write-host "Error: https://$hostname/certsrv/mscep/mscep.dll returns 200 OK. This usually signifies an error with the Intune Connector registering itself or not being installed." -BackgroundColor Red + Log-ScriptEvent $LogFilePath "https://$hostname/certsrv/mscep/mscep.dll returns 200 OK. This usually signifies an error with the Intune Connector registering itself or not being installed" NDES_Validation 3 + } + + elseif ($statuscode -eq "403"){ + + Write-Host "Trying to retrieve CA Capabilitiess..." -ForegroundColor Yellow + Write-Host + $Newstatuscode = try {(Invoke-WebRequest -Uri "https://$hostname/certsrv/mscep/mscep.dll?operation=GetCACaps&message=test").statuscode} catch {$_.Exception.Response.StatusCode.Value__} + + if ($Newstatuscode -eq "200"){ + + $CACaps = (Invoke-WebRequest -Uri "https://$hostname/certsrv/mscep?operation=GetCACaps&message=test").content + + } + + if ($CACaps){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "CA Capabilities retrieved:" + Write-Host + write-host $CACaps + Log-ScriptEvent $LogFilePath "CA Capabilities retrieved:$CACaps" NDES_Validation 1 + + } + + } + + else { + + Write-host "Error: Unexpected Error code! This usually signifies an error with the Intune Connector registering itself or not being installed" -BackgroundColor Red + Write-host "Expected value is a 403. We received a $($Statuscode). This could be down to a missing reboot post policy module install. Verify last boot time and module install time further down the validation." + Log-ScriptEvent $LogFilePath "Unexpected Error code. Expected:403|Received:$Statuscode" NDES_Validation 3 + + } + +#endregion + +################################################################# + +#region Checking Servers last boot time + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Servers last boot time..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Servers last boot time" NDES_Validation 1 + +$LastBoot = (Get-WmiObject win32_operatingsystem | select csname, @{LABEL='LastBootUpTime' +;EXPRESSION={$_.ConverttoDateTime($_.lastbootuptime)}}).lastbootuptime + +write-host "Server last rebooted: "-NoNewline +Write-Host "$($LastBoot). " -ForegroundColor Cyan -NoNewline +Write-Host "Please ensure a reboot has taken place _after_ all registry changes and installing the NDES Connector. IISRESET is _not_ sufficient." +Log-ScriptEvent $LogFilePath "LastBootTime:$LastBoot" NDES_Validation 1 + +#endregion + +################################################################# + +#region Checking Intune Connector is installed + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Intune Connector is installed..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Intune Connector is installed" NDES_Validation 1 + + if ($IntuneConnector = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | ? {$_.DisplayName -eq "Microsoft Intune Connector"}){ + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "$($IntuneConnector.DisplayName) was installed on " -NoNewline + Write-Host "$($IntuneConnector.InstallDate) " -ForegroundColor Cyan -NoNewline + write-host "and is version " -NoNewline + Write-Host "$($IntuneConnector.DisplayVersion)" -ForegroundColor Cyan -NoNewline + Write-host + Log-ScriptEvent $LogFilePath "ConnectorVersion:$IntuneConnector" NDES_Validation 1 + + } + + else { + + Write-Host "Error: Intune Connector not installed" -BackgroundColor red + Write-Host 'Please review "Step 5 - Enable, install, and configure the Intune certificate connector".' + write-host "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" + Write-Host + Log-ScriptEvent $LogFilePath "ConnectorNotInstalled" NDES_Validation 3 + + } + + +#endregion + +################################################################# + +#region Checking Intune Connector registry keys (KeyRecoveryAgentCertificate, PfxSigningCertificate and SigningCertificate) + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Intune Connector registry keys are intact" -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Intune Connector registry keys are intact" NDES_Validation 1 +$ErrorActionPreference = "SilentlyContinue" + +$KeyRecoveryAgentCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\KeyRecoveryAgentCertificate" +$PfxSigningCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\PfxSigningCertificate" +$SigningCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\SigningCertificate" + + if (-not ($KeyRecoveryAgentCertificate)){ + + Write-host "Error: KeyRecoveryAgentCertificate Registry key does not exist." -BackgroundColor Red + Write-Host + Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate Registry key does not exist." NDES_Validation 3 + + } + + else { + + $KeyRecoveryAgentCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name KeyRecoveryAgentCertificate).KeyRecoveryAgentCertificate + + if (-not ($KeyRecoveryAgentCertificatePresent)) { + + Write-Warning "KeyRecoveryAgentCertificate registry key exists but has no value" + Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate missing Value" NDES_Validation 2 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "KeyRecoveryAgentCertificate registry key exists" + Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate registry key exists" NDES_Validation 1 + + } + + + + } + + if (-not ($PfxSigningCertificate)){ + + Write-host "Error: PfxSigningCertificate Registry key does not exist." -BackgroundColor Red + Write-Host + Log-ScriptEvent $LogFilePath "PfxSigningCertificate Registry key does not exist." NDES_Validation 3 + + + } + + else { + + $PfxSigningCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name PfxSigningCertificate).PfxSigningCertificate + + if (-not ($PfxSigningCertificatePresent)) { + + Write-Warning "PfxSigningCertificate registry key exists but has no value" + Log-ScriptEvent $LogFilePath "PfxSigningCertificate missing Value" NDES_Validation 2 + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "PfxSigningCertificate registry keys exists" + Log-ScriptEvent $LogFilePath "PfxSigningCertificate registry key exists" NDES_Validation 1 + + } + + + + } + + if (-not ($SigningCertificate)){ + + Write-host "Error: SigningCertificate Registry key does not exist." -BackgroundColor Red + Write-Host + Log-ScriptEvent $LogFilePath "SigningCertificate Registry key does not exist" NDES_Validation 3 + + } + + else { + + $SigningCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name SigningCertificate).SigningCertificate + + if (-not ($SigningCertificatePresent)) { + + Write-Warning "SigningCertificate registry key exists but has no value" + Log-ScriptEvent $LogFilePath "SigningCertificate registry key exists but has no value" NDES_Validation 2 + + + } + + else { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + Write-Host "SigningCertificate registry key exists" + Log-ScriptEvent $LogFilePath "SigningCertificate registry key exists" NDES_Validation 1 + + + } + + + + } + +$ErrorActionPreference = "Continue" + +#endregion + +################################################################# + +#region Checking eventlog for pertinent errors + +$ErrorActionPreference = "SilentlyContinue" +$EventLogCollDays = ((Get-Date).AddDays(-5)) #Number of days to go back in the event log + +Write-host +Write-host "......................................................." +Write-host +Write-Host "Checking Event logs for pertinent errors..." -ForegroundColor Yellow +Write-host +Log-ScriptEvent $LogFilePath "Checking Event logs for pertinent errors" NDES_Validation 1 + + if (-not (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After $EventLogCollDays -ErrorAction silentlycontinue)) { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "No errors found in the Microsoft Intune Connector" + Write-host + Log-ScriptEvent $LogFilePath "No errors found in the Microsoft Intune Connector" NDES_Validation 1 + + } + + else { + + Write-Warning "Errors found in the Microsoft Intune Connector Event log. Please see below for the most recent 5, and investigate further in Event Viewer." + Write-Host + $EventsCol1 = (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After $EventLogCollDays -Newest 5 | select TimeGenerated,Source,Message) + $EventsCol1 | fl + Log-ScriptEvent $LogFilePath "Errors found in the Microsoft Intune Connector Event log" NDES_Eventvwr 3 + $i = 0 + $count = @($EventsCol1).count + + foreach ($item in $EventsCol1) { + + Log-ScriptEvent $LogFilePath "$($EventsCol1[$i].TimeGenerated);$($EventsCol1[$i].Message);$($EventsCol1[$i].Source)" NDES_Eventvwr 3 + $i++ + + } + + } + + if (-not (Get-EventLog -LogName "Application" -EntryType Error -Source NDESConnector,Microsoft-Windows-NetworkDeviceEnrollmentService -After $EventLogCollDays -ErrorAction silentlycontinue)) { + + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" + Write-host + Log-ScriptEvent $LogFilePath "No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" NDES_Validation 1 + + } + + else { + + Write-Warning "Errors found in the Application Event log for source NetworkDeviceEnrollmentService or NDESConnector. Please see below for the most recent 5, and investigate further in Event Viewer." + Write-Host + $EventsCol2 = (Get-EventLog -LogName "Application" -EntryType Error -Source NDESConnector,Microsoft-Windows-NetworkDeviceEnrollmentService -After $EventLogCollDays -Newest 5 | select TimeGenerated,Source,Message) + $EventsCol2 |fl + $i = 0 + $count = @($EventsCol2).count + + foreach ($item in $EventsCol2) { + + Log-ScriptEvent $LogFilePath "$($EventsCol2[$i].TimeGenerated);$($EventsCol2[$i].Message);$($EventsCol2[$i].Source)" NDES_Eventvwr 3 + $i++ + + } + +} + +$ErrorActionPreference = "Continue" + +#endregion + +################################################################# + +#region Zip up logfiles + +Write-host +Write-host "......................................................." +Write-host +Write-host "Log Files..." -ForegroundColor Yellow +Write-host +write-host "Do you want to gather troubleshooting files? This includes IIS, NDES Connector, NDES Plugin, CRP, and MSCEP log files, in addition to the SCEP template configuration. [Y]es, [N]o:" +$LogFileCollectionConfirmation = Read-Host + + if ($LogFileCollectionConfirmation -eq "y"){ + + $IISLogPath = (Get-WebConfigurationProperty "/system.applicationHost/sites/siteDefaults" -name logfile.directory).Value + "\W3SVC1" -replace "%SystemDrive%",$env:SystemDrive + $IISLogs = Get-ChildItem $IISLogPath| Sort-Object -Descending -Property LastWriteTime | Select-Object -First 3 + $NDESConnectorLogs = Get-ChildItem "C:\Program Files\Microsoft Intune\NDESConnectorSvc\Logs\Logs\NDESConnector*" | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 3 + $NDESPluginLogs = Get-ChildItem "C:\Program Files\Microsoft Intune\NDESPolicyModule\Logs\NDESPlugin.log" + $MSCEPLogs = Get-ChildItem "c:\users\*\mscep.log" | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 3 + $CRPLogs = Get-ChildItem "C:\Program Files\Microsoft Intune\NDESConnectorSvc\Logs\Logs\CertificateRegistrationPoint*" | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 3 + + foreach ($IISLog in $IISLogs){ + + Copy-Item -Path $IISLog.FullName -Destination $TempDirPath + + } + + foreach ($NDESConnectorLog in $NDESConnectorLogs){ + + Copy-Item -Path $NDESConnectorLog.FullName -Destination $TempDirPath + + } + + foreach ($NDESPluginLog in $NDESPluginLogs){ + + Copy-Item -Path $NDESPluginLog.FullName -Destination $TempDirPath + + } + + foreach ($MSCEPLog in $MSCEPLogs){ + + Copy-Item -Path $MSCEPLog.FullName -Destination $TempDirPath + + } + + foreach ($CRPLog in $CRPLogs){ + + Copy-Item -Path $CRPLogs.FullName -Destination $TempDirPath + + } + + $SCEPUserCertTemplateOutputFilePath = "$($TempDirPath)\SCEPUserCertTemplate.txt" + certutil -v -template $SCEPUserCertTemplate > $SCEPUserCertTemplateOutputFilePath + + Log-ScriptEvent $LogFilePath "Collecting server logs" NDES_Validation 1 + + Add-Type -assembly "system.io.compression.filesystem" + $Currentlocation = $env:temp + $date = Get-Date -Format ddMMyyhhmm + [io.compression.zipfile]::CreateFromDirectory($TempDirPath, "$($Currentlocation)\$($date)-Logs-$($hostname).zip") + + Write-host + Write-Host "Success: " -ForegroundColor Green -NoNewline + write-host "Log files copied to $($Currentlocation)\$($date)-Logs-$($hostname).zip" + Write-host + + } + + else { + + Log-ScriptEvent $LogFilePath "Do not collect logs" NDES_Validation 1 + $WriteLogOutputPath = $True + + } + + +#endregion + +################################################################# + +#region Ending script + +Write-host +Write-host "......................................................." +Write-host +Write-host "End of NDES configuration validation" -ForegroundColor Yellow +Write-Host + + if ($WriteLogOutputPath -eq $True) { + + write-host "Log file copied to $($LogFilePath)" + Write-Host + + } +write-host "Ending script..." -ForegroundColor Yellow +Write-host + +#endregion + +################################################################# + +} + +else { + +Write-Host +Write-host "......................................................." +Write-Host +Write-host "Incorrect variables. Please run the script again..." -ForegroundColor Red +Write-Host +Write-Host "Exiting................................................" +Write-Host +exit + +} From d2cbdd6d3ad4f02fb3b29c249212de27bcdb3af6 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:20:52 -0400 Subject: [PATCH 010/107] Add files via upload Remediation script pair for Intune Management Extension health check. --- Detect-IME.ps1 | 17 +++++++++++++++++ Remediate-IME.ps1 | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Detect-IME.ps1 create mode 100644 Remediate-IME.ps1 diff --git a/Detect-IME.ps1 b/Detect-IME.ps1 new file mode 100644 index 0000000..94e0af1 --- /dev/null +++ b/Detect-IME.ps1 @@ -0,0 +1,17 @@ +# Detection Script for Intune Management Engine with Exit Codes + +$serviceName = "IntuneManagementExtension" # Service name for Intune Management Engine +$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + +if ($service) { + if ($service.Status -eq 'Running') { + Write-Host "Service is running." + exit 0 # Exit code 0 for success, no remediation needed + } else { + Write-Host "Service is not running." + exit 1 # Exit code 1 for failure, trigger remediation + } +} else { + Write-Host "Service not found." + exit 1 # Exit code 1 for failure, trigger remediation +} diff --git a/Remediate-IME.ps1 b/Remediate-IME.ps1 new file mode 100644 index 0000000..749e18e --- /dev/null +++ b/Remediate-IME.ps1 @@ -0,0 +1,15 @@ +# Remediation Script for Intune Management Engine + +$serviceName = "IntuneManagementExtension" # Service name for Intune Management Engine +$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + +if ($service) { + if ($service.Status -ne 'Running') { + Start-Service -Name $serviceName + Write-Output "Service started" + } else { + Write-Output "Service already running" + } +} else { + Write-Output "Service not found" +} From 06a8acaa543dec76d5d4d70487efcd948897568b Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 17:26:27 -0400 Subject: [PATCH 011/107] Update Check-ADHealth.ps1 Modified log collection. --- Check-ADHealth.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Check-ADHealth.ps1 b/Check-ADHealth.ps1 index 05c5ffc..2602f93 100644 --- a/Check-ADHealth.ps1 +++ b/Check-ADHealth.ps1 @@ -49,10 +49,19 @@ foreach ($DC in $DCs) { # Collect Application and System Log for each DC foreach ($DC in $DCs) { Write-Output "Collecting Application and System Log for $DC" - Get-EventLog -LogName application -ComputerName $DC.Name -Newest 5000 | Out-File -FilePath .\$org\AppLog_$($DC.Name).txt - Get-EventLog -LogName system -ComputerName $DC.Name -Newest 5000 | Out-File -FilePath .\$org\SysLog_$($DC.Name).txt + + # Collect and export the Application log + Get-EventLog -LogName application -ComputerName $DC.Name -Newest 5000 | + Select-Object TimeGenerated, Source, EventID, EntryType, Message | + Export-Csv -Path ".\$org\AppLog_$($DC.Name).csv" -NoTypeInformation + + # Collect and export the System log + Get-EventLog -LogName system -ComputerName $DC.Name -Newest 5000 | + Select-Object TimeGenerated, Source, EventID, EntryType, Message | + Export-Csv -Path ".\$org\SysLog_$($DC.Name).csv" -NoTypeInformation } + # Create computer subdirectory for each DC foreach ($DC in $DCs) { Write-Output "Creating subdirectory for $DC" From f19bad5e8def0a95d2a6ddad2a5790024ab0f5da Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 18:38:30 -0400 Subject: [PATCH 012/107] Update Check-ADHealth.ps1 Add FSMO roles. --- Check-ADHealth.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Check-ADHealth.ps1 b/Check-ADHealth.ps1 index 2602f93..088776c 100644 --- a/Check-ADHealth.ps1 +++ b/Check-ADHealth.ps1 @@ -45,6 +45,21 @@ foreach ($DC in $DCs) { $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath .\$org\HotFixes_$($DC.Name).txt } +# Collect FSMO Roles Information +Write-Output "Collecting FSMO Roles Information" +$forest = Get-ADForest +$domain = Get-ADDomain + +$FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster; + "Domain Naming Master" = $forest.DomainNamingMaster; + "Infrastructure Master" = $domain.InfrastructureMaster; + "RID Master" = $domain.RIDMaster; + "PDC Emulator" = $domain.PDCEmulator; +} + +$FSMORoles | Out-File -FilePath .\$org\FSMORoles.txt + # Collect Application and System Log for each DC foreach ($DC in $DCs) { From be02229f3a2ff5850497144a6d04cf4c2e46f405 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 18:45:49 -0400 Subject: [PATCH 013/107] Create Check-ADHealth_NEW.ps1 NEW AD Health Check script. --- Check-ADHealth_NEW.ps1 | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Check-ADHealth_NEW.ps1 diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 new file mode 100644 index 0000000..d2727ce --- /dev/null +++ b/Check-ADHealth_NEW.ps1 @@ -0,0 +1,79 @@ +$comp = $env:computername +$org = "Change to Company Name" +$FormatEnumerationLimit = -1 + +# Logging function +function Write-Log { + param([string]$message) + "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')): $message" | Out-File -FilePath ".\$org\script_log.txt" -Append +} + +# Error handling function +function Handle-Error { + param($error) + Write-Log "ERROR: $error" +} + +# Ensure the ActiveDirectory module is available +if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { + Write-Host "ActiveDirectory module is not installed. Exiting script." + exit +} + +Import-Module -Name ActiveDirectory -ErrorAction Stop + +Write-Log "Creating directory for $org" +New-Item -Path . -Name $org -ItemType Directory -ErrorAction Continue + +# Start the transcript with a new file name including "transcript" +$transcriptFileName = ".\$org\${comp}_transcript.txt" +Start-Transcript -Path $transcriptFileName -ErrorAction Continue + +# Collecting various AD information using modular functions +function Collect-ADInformation { + Write-Log "Collecting list of Active Directory sites" + Get-ADReplicationSite -Filter * | Format-Table -AutoSize | Out-File -FilePath ".\$org\Sites.txt" + + Write-Log "Collecting FSMO Roles Information" + $forest = Get-ADForest + $domain = Get-ADDomain + + $FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster; + "Domain Naming Master" = $forest.DomainNamingMaster; + "Infrastructure Master" = $domain.InfrastructureMaster; + "RID Master" = $domain.RIDMaster; + "PDC Emulator" = $domain.PDCEmulator; + } + + $FSMORoles | Out-File -FilePath ".\$org\FSMORoles.txt" +} + +# Run the Collect-ADInformation function +Collect-ADInformation + +# Collecting data using Invoke-Command for better performance +$DCs = Get-ADDomainController -Filter * + +foreach ($DC in $DCs) { + $scriptBlock = { + param($org) + # Replication information + repadmin /showrepl $env:COMPUTERNAME | Out-File -FilePath ".\$org\ReplicationInfo_$env:COMPUTERNAME.txt" + + # HotFix information + $hotfixes = Get-WmiObject -Class Win32_QuickFixEngineering + $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath ".\$org\HotFixes_$env:COMPUTERNAME.txt" + + # System and Application logs + Get-EventLog -LogName application -Newest 5000 | + Export-Csv -Path ".\$org\AppLog_$env:COMPUTERNAME.csv" -NoTypeInformation + + Get-EventLog -LogName system -Newest 5000 | + Export-Csv -Path ".\$org\SysLog_$env:COMPUTERNAME.csv" -NoTypeInformation + } + Invoke-Command -ComputerName $DC.Name -ScriptBlock $scriptBlock -ArgumentList $org -ErrorAction Continue +} + +Stop-Transcript +Write-Log "Script execution completed" From 90ae95c62b6a823b3f6558da3a97d565a81835ee Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 18:55:44 -0400 Subject: [PATCH 014/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 84 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index d2727ce..1d8b652 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,79 +1,77 @@ $comp = $env:computername -$org = "Change to Company Name" -$FormatEnumerationLimit = -1 +$org = "YourCompanyName" # Change this to your actual company name +$orgDir = Join-Path -Path "." -ChildPath $org -# Logging function +# Function to log messages function Write-Log { param([string]$message) - "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')): $message" | Out-File -FilePath ".\$org\script_log.txt" -Append + $logPath = Join-Path -Path $orgDir -ChildPath "script_log.txt" + "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')): $message" | Out-File -FilePath $logPath -Append } -# Error handling function +# Function to handle errors function Handle-Error { param($error) Write-Log "ERROR: $error" } -# Ensure the ActiveDirectory module is available -if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { - Write-Host "ActiveDirectory module is not installed. Exiting script." - exit +# Check and create directory +if (-not (Test-Path -Path $orgDir)) { + New-Item -Path $orgDir -ItemType Directory -Force | Out-Null + Write-Log "Created directory: $orgDir" } -Import-Module -Name ActiveDirectory -ErrorAction Stop - -Write-Log "Creating directory for $org" -New-Item -Path . -Name $org -ItemType Directory -ErrorAction Continue - -# Start the transcript with a new file name including "transcript" -$transcriptFileName = ".\$org\${comp}_transcript.txt" +# Start the transcript with a new file name +$transcriptFileName = Join-Path -Path $orgDir -ChildPath "${comp}_transcript.txt" Start-Transcript -Path $transcriptFileName -ErrorAction Continue -# Collecting various AD information using modular functions -function Collect-ADInformation { - Write-Log "Collecting list of Active Directory sites" - Get-ADReplicationSite -Filter * | Format-Table -AutoSize | Out-File -FilePath ".\$org\Sites.txt" +# Collect FSMO Roles Information +function Collect-FSMORoles { + try { + $forest = Get-ADForest + $domain = Get-ADDomain - Write-Log "Collecting FSMO Roles Information" - $forest = Get-ADForest - $domain = Get-ADDomain + $FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster; + "Domain Naming Master" = $forest.DomainNamingMaster; + "Infrastructure Master" = $domain.InfrastructureMaster; + "RID Master" = $domain.RIDMaster; + "PDC Emulator" = $domain.PDCEmulator; + } - $FSMORoles = @{ - "Schema Master" = $forest.SchemaMaster; - "Domain Naming Master" = $forest.DomainNamingMaster; - "Infrastructure Master" = $domain.InfrastructureMaster; - "RID Master" = $domain.RIDMaster; - "PDC Emulator" = $domain.PDCEmulator; + $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" + $FSMORoles | Out-File -FilePath $FSMORolesFilePath + Write-Log "FSMO roles collected successfully." + } catch { + Handle-Error $_.Exception.Message } - - $FSMORoles | Out-File -FilePath ".\$org\FSMORoles.txt" } -# Run the Collect-ADInformation function -Collect-ADInformation +# Run FSMO roles collection +Collect-FSMORoles # Collecting data using Invoke-Command for better performance $DCs = Get-ADDomainController -Filter * - foreach ($DC in $DCs) { $scriptBlock = { param($org) # Replication information - repadmin /showrepl $env:COMPUTERNAME | Out-File -FilePath ".\$org\ReplicationInfo_$env:COMPUTERNAME.txt" + $replicationInfoPath = Join-Path -Path $org -ChildPath ("ReplicationInfo_$env:COMPUTERNAME.txt") + repadmin /showrepl | Out-File -FilePath $replicationInfoPath # HotFix information + $hotFixesPath = Join-Path -Path $org -ChildPath ("HotFixes_$env:COMPUTERNAME.txt") $hotfixes = Get-WmiObject -Class Win32_QuickFixEngineering - $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath ".\$org\HotFixes_$env:COMPUTERNAME.txt" + $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath $hotFixesPath # System and Application logs - Get-EventLog -LogName application -Newest 5000 | - Export-Csv -Path ".\$org\AppLog_$env:COMPUTERNAME.csv" -NoTypeInformation - - Get-EventLog -LogName system -Newest 5000 | - Export-Csv -Path ".\$org\SysLog_$env:COMPUTERNAME.csv" -NoTypeInformation + $appLogPath = Join-Path -Path $org -ChildPath ("AppLog_$env:COMPUTERNAME.csv") + $sysLogPath = Join-Path -Path $org -ChildPath ("SysLog_$env:COMPUTERNAME.csv") + Get-EventLog -LogName application -Newest 5000 | Export-Csv -Path $appLogPath -NoTypeInformation + Get-EventLog -LogName system -Newest 5000 | Export-Csv -Path $sysLogPath -NoTypeInformation } - Invoke-Command -ComputerName $DC.Name -ScriptBlock $scriptBlock -ArgumentList $org -ErrorAction Continue + Invoke-Command -ComputerName $DC.Name -ScriptBlock $scriptBlock -ArgumentList $orgDir -ErrorAction Continue } Stop-Transcript -Write-Log "Script execution completed" +Write-Log "Script execution completed." From c3f47cc181e86c046e379123f5c8901623bc5c3f Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 18:59:57 -0400 Subject: [PATCH 015/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 1d8b652..eb183a6 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,3 +1,14 @@ +# Define the base directory +$baseDir = "C:\Temp" +$orgDir = Join-Path -Path $baseDir -ChildPath $org + +# Check and create the base directory if it does not exist +if (-not (Test-Path -Path $baseDir)) { + New-Item -Path $baseDir -ItemType Directory -Force | Out-Null + Write-Host "Created base directory: $baseDir" +} + +# Now use $orgDir as the base for all other file operations $comp = $env:computername $org = "YourCompanyName" # Change this to your actual company name $orgDir = Join-Path -Path "." -ChildPath $org From 3e16fa9bf770372ad41678b776e43db0e86ea88c Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 19:03:28 -0400 Subject: [PATCH 016/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index eb183a6..38cce8c 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,3 +1,8 @@ +# Now use $orgDir as the base for all other file operations +$comp = $env:computername +$org = "YourCompanyName" # Change this to your actual company name +$orgDir = Join-Path -Path "." -ChildPath $org + # Define the base directory $baseDir = "C:\Temp" $orgDir = Join-Path -Path $baseDir -ChildPath $org @@ -8,10 +13,6 @@ if (-not (Test-Path -Path $baseDir)) { Write-Host "Created base directory: $baseDir" } -# Now use $orgDir as the base for all other file operations -$comp = $env:computername -$org = "YourCompanyName" # Change this to your actual company name -$orgDir = Join-Path -Path "." -ChildPath $org # Function to log messages function Write-Log { From d493345f28774320940287df00c7c1dd34638a0b Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 19:14:23 -0400 Subject: [PATCH 017/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 97 ++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 38cce8c..3a9515b 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,20 +1,9 @@ -# Now use $orgDir as the base for all other file operations $comp = $env:computername $org = "YourCompanyName" # Change this to your actual company name -$orgDir = Join-Path -Path "." -ChildPath $org - -# Define the base directory -$baseDir = "C:\Temp" +$baseDir = "D:\Temp" # Base directory for script execution $orgDir = Join-Path -Path $baseDir -ChildPath $org -# Check and create the base directory if it does not exist -if (-not (Test-Path -Path $baseDir)) { - New-Item -Path $baseDir -ItemType Directory -Force | Out-Null - Write-Host "Created base directory: $baseDir" -} - - -# Function to log messages +# Function to write logs function Write-Log { param([string]$message) $logPath = Join-Path -Path $orgDir -ChildPath "script_log.txt" @@ -27,63 +16,61 @@ function Handle-Error { Write-Log "ERROR: $error" } -# Check and create directory +# Check and create the base and organization directories +if (-not (Test-Path -Path $baseDir)) { + New-Item -Path $baseDir -ItemType Directory -Force | Out-Null + Write-Log "Created base directory: $baseDir" +} if (-not (Test-Path -Path $orgDir)) { New-Item -Path $orgDir -ItemType Directory -Force | Out-Null - Write-Log "Created directory: $orgDir" + Write-Log "Created organization directory: $orgDir" } -# Start the transcript with a new file name +# Start the transcript $transcriptFileName = Join-Path -Path $orgDir -ChildPath "${comp}_transcript.txt" Start-Transcript -Path $transcriptFileName -ErrorAction Continue +Write-Log "Started transcript at $transcriptFileName" # Collect FSMO Roles Information -function Collect-FSMORoles { - try { - $forest = Get-ADForest - $domain = Get-ADDomain - - $FSMORoles = @{ - "Schema Master" = $forest.SchemaMaster; - "Domain Naming Master" = $forest.DomainNamingMaster; - "Infrastructure Master" = $domain.InfrastructureMaster; - "RID Master" = $domain.RIDMaster; - "PDC Emulator" = $domain.PDCEmulator; - } +try { + $forest = Get-ADForest + $domain = Get-ADDomain - $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" - $FSMORoles | Out-File -FilePath $FSMORolesFilePath - Write-Log "FSMO roles collected successfully." - } catch { - Handle-Error $_.Exception.Message + $FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster; + "Domain Naming Master" = $forest.DomainNamingMaster; + "Infrastructure Master" = $domain.InfrastructureMaster; + "RID Master" = $domain.RIDMaster; + "PDC Emulator" = $domain.PDCEmulator; } -} -# Run FSMO roles collection -Collect-FSMORoles + $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" + $FSMORoles | Out-File -FilePath $FSMORolesFilePath + Write-Log "FSMO roles collected successfully and written to $FSMORolesFilePath" +} catch { + Handle-Error $_.Exception.Message +} -# Collecting data using Invoke-Command for better performance +# Handling domain controllers $DCs = Get-ADDomainController -Filter * foreach ($DC in $DCs) { - $scriptBlock = { - param($org) - # Replication information - $replicationInfoPath = Join-Path -Path $org -ChildPath ("ReplicationInfo_$env:COMPUTERNAME.txt") - repadmin /showrepl | Out-File -FilePath $replicationInfoPath - - # HotFix information - $hotFixesPath = Join-Path -Path $org -ChildPath ("HotFixes_$env:COMPUTERNAME.txt") - $hotfixes = Get-WmiObject -Class Win32_QuickFixEngineering - $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath $hotFixesPath - - # System and Application logs - $appLogPath = Join-Path -Path $org -ChildPath ("AppLog_$env:COMPUTERNAME.csv") - $sysLogPath = Join-Path -Path $org -ChildPath ("SysLog_$env:COMPUTERNAME.csv") - Get-EventLog -LogName application -Newest 5000 | Export-Csv -Path $appLogPath -NoTypeInformation - Get-EventLog -LogName system -Newest 5000 | Export-Csv -Path $sysLogPath -NoTypeInformation + $dcPath = Join-Path -Path $orgDir -ChildPath ($DC.Name -replace "[^a-zA-Z0-9]", "_") + + # Check and create directory for each DC + if (-not (Test-Path -Path $dcPath)) { + New-Item -Path $dcPath -ItemType Directory -Force | Out-Null + Write-Log "Created directory for DC: $dcPath" + } + + # Perform operations for each DC + $filePath = Join-Path -Path $dcPath -ChildPath "info.txt" + try { + "Information for $DC" | Out-File -Path $filePath + Write-Log "Successfully wrote information for $($DC.Name) to $filePath" + } catch { + Handle-Error $_.Exception.Message } - Invoke-Command -ComputerName $DC.Name -ScriptBlock $scriptBlock -ArgumentList $orgDir -ErrorAction Continue } Stop-Transcript -Write-Log "Script execution completed." +Write-Log "Script execution completed and transcript stopped." From c39b8692f99828bbcf789b2f739f0aab42fa6812 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 19:17:41 -0400 Subject: [PATCH 018/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 48 ++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 3a9515b..4a211a9 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,6 +1,6 @@ $comp = $env:computername -$org = "YourCompanyName" # Change this to your actual company name -$baseDir = "D:\Temp" # Base directory for script execution +$org = "YourCompanyName" # Change this to your actual company name +$baseDir = "D:\Temp" # Base directory for script execution $orgDir = Join-Path -Path $baseDir -ChildPath $org # Function to write logs @@ -31,42 +31,34 @@ $transcriptFileName = Join-Path -Path $orgDir -ChildPath "${comp}_transcript.txt Start-Transcript -Path $transcriptFileName -ErrorAction Continue Write-Log "Started transcript at $transcriptFileName" -# Collect FSMO Roles Information -try { - $forest = Get-ADForest - $domain = Get-ADDomain - - $FSMORoles = @{ - "Schema Master" = $forest.SchemaMaster; - "Domain Naming Master" = $forest.DomainNamingMaster; - "Infrastructure Master" = $domain.InfrastructureMaster; - "RID Master" = $domain.RIDMaster; - "PDC Emulator" = $domain.PDCEmulator; - } - - $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" - $FSMORoles | Out-File -FilePath $FSMORolesFilePath - Write-Log "FSMO roles collected successfully and written to $FSMORolesFilePath" -} catch { - Handle-Error $_.Exception.Message -} - -# Handling domain controllers +# Collecting data for each Domain Controller $DCs = Get-ADDomainController -Filter * foreach ($DC in $DCs) { $dcPath = Join-Path -Path $orgDir -ChildPath ($DC.Name -replace "[^a-zA-Z0-9]", "_") - # Check and create directory for each DC + # Ensure directory for each DC if (-not (Test-Path -Path $dcPath)) { New-Item -Path $dcPath -ItemType Directory -Force | Out-Null Write-Log "Created directory for DC: $dcPath" } - # Perform operations for each DC - $filePath = Join-Path -Path $dcPath -ChildPath "info.txt" try { - "Information for $DC" | Out-File -Path $filePath - Write-Log "Successfully wrote information for $($DC.Name) to $filePath" + # Replication information + $replicationFilePath = Join-Path -Path $dcPath -ChildPath "replication_info.txt" + repadmin /showrepl $DC.HostName | Out-File -Path $replicationFilePath + Write-Log "Replication info written for $($DC.Name)" + + # HotFix information + $hotFixFilePath = Join-Path -Path $dcPath -ChildPath "hotfixes.txt" + Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -Path $hotFixFilePath + Write-Log "Hotfix info written for $($DC.Name)" + + # System and Application logs + $appLogPath = Join-Path -Path $dcPath -ChildPath "app_log.csv" + Get-EventLog -LogName Application -Newest 50 -ComputerName $DC.Name | Select-Object TimeGenerated, Source, InstanceID, Message | Export-Csv -Path $appLogPath -NoTypeInformation + $sysLogPath = Join-Path -Path $dcPath -ChildPath "sys_log.csv" + Get-EventLog -LogName System -Newest 50 -ComputerName $DC.Name | Select-Object TimeGenerated, Source, InstanceID, Message | Export-Csv -Path $sysLogPath -NoTypeInformation + Write-Log "Event logs written for $($DC.Name)" } catch { Handle-Error $_.Exception.Message } From 480793d51af782653c742fc05810bdaa7d7417a3 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 19:22:12 -0400 Subject: [PATCH 019/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 4a211a9..84e6b71 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -45,12 +45,12 @@ foreach ($DC in $DCs) { try { # Replication information $replicationFilePath = Join-Path -Path $dcPath -ChildPath "replication_info.txt" - repadmin /showrepl $DC.HostName | Out-File -Path $replicationFilePath + repadmin /showrepl $DC.HostName | Out-File -FilePath $replicationFilePath Write-Log "Replication info written for $($DC.Name)" # HotFix information $hotFixFilePath = Join-Path -Path $dcPath -ChildPath "hotfixes.txt" - Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -Path $hotFixFilePath + Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -FilePath $hotFixFilePath Write-Log "Hotfix info written for $($DC.Name)" # System and Application logs From f391627c317622cbf05f1b45471e10ac4be6c425 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 19:30:18 -0400 Subject: [PATCH 020/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 73 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 84e6b71..86027b9 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -1,5 +1,5 @@ $comp = $env:computername -$org = "YourCompanyName" # Change this to your actual company name +$org = "YourCompanyName" # Update with your organization's name $baseDir = "D:\Temp" # Base directory for script execution $orgDir = Join-Path -Path $baseDir -ChildPath $org @@ -16,7 +16,7 @@ function Handle-Error { Write-Log "ERROR: $error" } -# Check and create the base and organization directories +# Ensure base and organization directories exist if (-not (Test-Path -Path $baseDir)) { New-Item -Path $baseDir -ItemType Directory -Force | Out-Null Write-Log "Created base directory: $baseDir" @@ -26,13 +26,62 @@ if (-not (Test-Path -Path $orgDir)) { Write-Log "Created organization directory: $orgDir" } -# Start the transcript +# Start transcript $transcriptFileName = Join-Path -Path $orgDir -ChildPath "${comp}_transcript.txt" Start-Transcript -Path $transcriptFileName -ErrorAction Continue Write-Log "Started transcript at $transcriptFileName" -# Collecting data for each Domain Controller -$DCs = Get-ADDomainController -Filter * +# Collect FSMO Roles +try { + $forest = Get-ADForest + $domain = Get-ADDomain + + $FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster; + "Domain Naming Master" = $forest.DomainNamingMaster; + "Infrastructure Master" = $domain.InfrastructureMaster; + "RID Master" = $domain.RIDMaster; + "PDC Emulator" = $domain.PDCEmulator; + } + + $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" + $FSMORoles | Out-File -FilePath $FSMORolesFilePath + Write-Log "FSMO roles collected and written to $FSMORolesFilePath" +} catch { + Handle-Error $_.Exception.Message +} + +# Collect AD Sites Information +try { + $sitesInfo = Get-ADReplicationSite -Filter * | Format-Table -AutoSize + $sitesFilePath = Join-Path -Path $orgDir -ChildPath "ADSites.txt" + $sitesInfo | Out-File -FilePath $sitesFilePath + Write-Log "Active Directory sites information collected and written to $sitesFilePath" +} catch { + Handle-Error $_.Exception.Message +} + +# Collect Global Catalog Servers +try { + $gcs = Get-ADDomainController -Filter {IsGlobalCatalog -eq $true} | Select-Object Name, HostName, Site + $gcsFilePath = Join-Path -Path $orgDir -ChildPath "GlobalCatalogs.txt" + $gcs | Out-File -FilePath $gcsFilePath + Write-Log "Global Catalog servers information collected and written to $gcsFilePath" +} catch { + Handle-Error $_.Exception.Message +} + +# Collect DC List +try { + $DCs = Get-ADDomainController -Filter * + $DCsFilePath = Join-Path -Path $orgDir -ChildPath "DomainControllers.txt" + $DCs | Select-Object Name, HostName, Site | Out-File -FilePath $DCsFilePath + Write-Log "Domain controllers list collected and written to $DCsFilePath" +} catch { + Handle-Error $_.Exception.Message +} + +# Operations for each Domain Controller foreach ($DC in $DCs) { $dcPath = Join-Path -Path $orgDir -ChildPath ($DC.Name -replace "[^a-zA-Z0-9]", "_") @@ -46,19 +95,25 @@ foreach ($DC in $DCs) { # Replication information $replicationFilePath = Join-Path -Path $dcPath -ChildPath "replication_info.txt" repadmin /showrepl $DC.HostName | Out-File -FilePath $replicationFilePath - Write-Log "Replication info written for $($DC.Name)" + Write-Log "Replication info for $($DC.Name) written to $replicationFilePath" # HotFix information $hotFixFilePath = Join-Path -Path $dcPath -ChildPath "hotfixes.txt" Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -FilePath $hotFixFilePath - Write-Log "Hotfix info written for $($DC.Name)" + Write-Log "Hotfix info for $($DC.Name) written to $hotFixFilePath" - # System and Application logs + # Application and System logs $appLogPath = Join-Path -Path $dcPath -ChildPath "app_log.csv" Get-EventLog -LogName Application -Newest 50 -ComputerName $DC.Name | Select-Object TimeGenerated, Source, InstanceID, Message | Export-Csv -Path $appLogPath -NoTypeInformation $sysLogPath = Join-Path -Path $dcPath -ChildPath "sys_log.csv" Get-EventLog -LogName System -Newest 50 -ComputerName $DC.Name | Select-Object TimeGenerated, Source, InstanceID, Message | Export-Csv -Path $sysLogPath -NoTypeInformation - Write-Log "Event logs written for $($DC.Name)" + Write-Log "Application and system logs for $($DC.Name) written to $appLogPath and $sysLogPath" + + # Copy netlogon.log + $netlogonSource = "\\$($DC.HostName)\c$\Windows\debug\netlogon.log" + $netlogonDest = Join-Path -Path $dcPath -ChildPath "netlogon.log" + Copy-Item -Path $netlogonSource -Destination $netlogonDest -ErrorAction Continue + Write-Log "Netlogon log for $($DC.Name) copied to $netlogonDest" } catch { Handle-Error $_.Exception.Message } From a9e84a1e3705d295a9bd0974bb3712cc4b93dbab Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 15 May 2024 21:40:42 -0400 Subject: [PATCH 021/107] Update Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index 86027b9..e093c36 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -16,6 +16,28 @@ function Handle-Error { Write-Log "ERROR: $error" } +function Check-ADBackup { + param( + [string]$DCName, + [string]$dcPath # Added parameter for the DC-specific path + ) + try { + $backupCheck = Get-ADObject -Identity "CN=NTDS Settings,CN=$DCName,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,$((Get-ADDomain).DistinguishedName)" -Properties lastBackup -Server $DCName + if ($backupCheck.lastBackup) { + $lastBackupTime = [DateTime]::FromFileTime($backupCheck.lastBackup) + $message = "Last backup of AD on DC $DCName was at $lastBackupTime" + } else { + $message = "No backup information found for AD on DC $DCName" + } + Write-Log $message # Keep the centralized logging + $backupFilePath = Join-Path -Path $dcPath -ChildPath "ADBackupStatus.txt" + $message | Out-File -FilePath $backupFilePath # Write to a DC-specific file + } catch { + Handle-Error "Failed to check AD backup status for $DCName: $_" + } +} + + # Ensure base and organization directories exist if (-not (Test-Path -Path $baseDir)) { New-Item -Path $baseDir -ItemType Directory -Force | Out-Null @@ -102,6 +124,10 @@ foreach ($DC in $DCs) { Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -FilePath $hotFixFilePath Write-Log "Hotfix info for $($DC.Name) written to $hotFixFilePath" + # Check AD backup status + Check-ADBackup -DCName $DC.Name -dcPath $dcPath + + # Application and System logs $appLogPath = Join-Path -Path $dcPath -ChildPath "app_log.csv" Get-EventLog -LogName Application -Newest 50 -ComputerName $DC.Name | Select-Object TimeGenerated, Source, InstanceID, Message | Export-Csv -Path $appLogPath -NoTypeInformation From 95238e159fc7acd75c61883e24b756b37f2ede0e Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:32:11 -0400 Subject: [PATCH 022/107] Add files via upload Checks and sets computername via proactive remediation. --- Apply-NameTemplate.ps1 | 10 ++++++++++ Check-NameTemplate.ps1 | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Apply-NameTemplate.ps1 create mode 100644 Check-NameTemplate.ps1 diff --git a/Apply-NameTemplate.ps1 b/Apply-NameTemplate.ps1 new file mode 100644 index 0000000..3e29a4b --- /dev/null +++ b/Apply-NameTemplate.ps1 @@ -0,0 +1,10 @@ +# Remediation Script + +# Get the serial number of the device +$serialNumber = (Get-WmiObject -Class Win32_BIOS).SerialNumber + +# Construct the new computer name +$newComputerName = "AP-$serialNumber" + +# Rename the computer +Rename-Computer -NewName $newComputerName -Force -Restart diff --git a/Check-NameTemplate.ps1 b/Check-NameTemplate.ps1 new file mode 100644 index 0000000..9265021 --- /dev/null +++ b/Check-NameTemplate.ps1 @@ -0,0 +1,19 @@ +# Detection Script + +# Get the serial number of the device +$serialNumber = (Get-WmiObject -Class Win32_BIOS).SerialNumber + +# Get the current computer name +$computerName = $env:COMPUTERNAME + +# Construct the expected computer name +$expectedName = "AP-$serialNumber" + +# Check if the current computer name matches the expected name +if ($computerName -eq $expectedName) { + Write-Output "Device name is correct: $computerName" + exit 0 +} else { + Write-Output "Device name is incorrect: $computerName. Expected: $expectedName" + exit 1 +} From 6ac286b0be095aae88e0be7cd4d4567a82525fa5 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:31:07 -0700 Subject: [PATCH 023/107] Add files via upload Intune Group Policy Analytics cleanup --- Delete-GroupPolicyAnalyticsReports.ps1 | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Delete-GroupPolicyAnalyticsReports.ps1 diff --git a/Delete-GroupPolicyAnalyticsReports.ps1 b/Delete-GroupPolicyAnalyticsReports.ps1 new file mode 100644 index 0000000..3968726 --- /dev/null +++ b/Delete-GroupPolicyAnalyticsReports.ps1 @@ -0,0 +1,23 @@ +# Ensure Microsoft Graph PowerShell SDK is installed +if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { + Install-Module Microsoft.Graph -Scope CurrentUser -Force +} + +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All" + +# Get Group Policy Analytics reports +$reports = Get-MgDeviceManagementGroupPolicyMigrationReport + +# Display the reports in a grid view for selection +$selectedReports = $reports | Out-GridView -Title "Select Group Policy Analytics Reports to Delete" -PassThru + +# Delete the selected reports +foreach ($report in $selectedReports) { + try { + Remove-MgDeviceManagementGroupPolicyMigrationReport -GroupPolicyMigrationReportId $report.Id + Write-Output "Successfully deleted report with ID: $($report.Id)" + } catch { + Write-Error "Failed to delete report with ID: $($report.Id) - $_" + } +} From 0e361a0f885e6a14af83dc0deac48e7ce57efc55 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:45:38 -0400 Subject: [PATCH 024/107] Add files via upload Retrieve BitLocker keys, device names, and device IDs from Entra using Graph SDK --- Get-BitLockerInfo.ps1 | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Get-BitLockerInfo.ps1 diff --git a/Get-BitLockerInfo.ps1 b/Get-BitLockerInfo.ps1 new file mode 100644 index 0000000..b79367f --- /dev/null +++ b/Get-BitLockerInfo.ps1 @@ -0,0 +1,28 @@ +# Connect to Microsoft Graph with the required scopes +Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All", "BitLockerKey.Read.All" +Import-Module Microsoft.Graph.Beta.Identity.DirectoryManagement + +# Get all managed devices +write-host "Getting device information" +$devices = Get-MgBetaDevice + +# Get BitLocker Recovery Keys +write-host "Getting bitlocker key" +$bitlockerKeys = Get-MgInformationProtectionBitlockerRecoveryKey -All | select Id,CreatedDateTime,DeviceId,@{n="Key";e={(Get-MgInformationProtectionBitlockerRecoveryKey -BitlockerRecoveryKeyId $_.Id -Property key).key}},VolumeType + +# Combine the device details with their corresponding BitLocker recovery keys +$results = foreach ($key in $bitlockerKeys) { + write-host "Checking on key $key" -foreground cyan + $device = $devices | Where-Object { $_.DeviceId -eq $key.DeviceId } + [PSCustomObject]@{ + DeviceName = $device.DisplayName # Adjust based on actual property found + DeviceId = $key.DeviceId + RecoveryKey = $key.Key + VolumeType = $key.VolumeType + CreatedDateTime = $key.CreatedDateTime + } + write-host "Checking on $device.DisplayName" +} + +# Display the results in a grid view +$results | Out-GridView From 4387919a380d0860ffb07f0cc951ad89a5cc5f05 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:45:14 -0400 Subject: [PATCH 025/107] Add files via upload Intune cache cleanup files for remediation --- Detect-IntuneCache.ps1 | 9 +++++++++ Remediate-IntuneCache.ps1 | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 Detect-IntuneCache.ps1 create mode 100644 Remediate-IntuneCache.ps1 diff --git a/Detect-IntuneCache.ps1 b/Detect-IntuneCache.ps1 new file mode 100644 index 0000000..94caa53 --- /dev/null +++ b/Detect-IntuneCache.ps1 @@ -0,0 +1,9 @@ +# Detection Script + +$msiFiles = Get-ChildItem -Path 'C:\Windows\System32\config\systemprofile\AppData\Local\mdm' -Filter *.msi -File -ErrorAction SilentlyContinue + +if ($msiFiles.Count -gt 0) { + exit 1 # Remediation needed +} else { + exit 0 # No remediation needed +} diff --git a/Remediate-IntuneCache.ps1 b/Remediate-IntuneCache.ps1 new file mode 100644 index 0000000..9e75e60 --- /dev/null +++ b/Remediate-IntuneCache.ps1 @@ -0,0 +1,5 @@ +# Remediation Script + +Invoke-Command -ScriptBlock { + Get-ChildItem -File -Filter *.msi -Path 'C:\Windows\System32\config\systemprofile\AppData\Local\mdm' -Force -ErrorAction SilentlyContinue | Where-Object {$_.length -gt 1KB} | Remove-Item -Force +} From 228c3441bb21fe6445cde9a1bdcf7cc8230ba4aa Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:55:47 -0400 Subject: [PATCH 026/107] Update Detect-IntuneCache.ps1 Message output added --- Detect-IntuneCache.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Detect-IntuneCache.ps1 b/Detect-IntuneCache.ps1 index 94caa53..b917322 100644 --- a/Detect-IntuneCache.ps1 +++ b/Detect-IntuneCache.ps1 @@ -3,7 +3,9 @@ $msiFiles = Get-ChildItem -Path 'C:\Windows\System32\config\systemprofile\AppData\Local\mdm' -Filter *.msi -File -ErrorAction SilentlyContinue if ($msiFiles.Count -gt 0) { + write-host "Remediation needed" -foregroundcolor red exit 1 # Remediation needed } else { + write-host "No remediation needed" -foregroundcolor green exit 0 # No remediation needed } From 4b60a58cdeabcd465a45c5e0aa2e6d94ec37b5cb Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:42:37 -0400 Subject: [PATCH 027/107] Update Detect-IntuneCache.ps1 Edited for MDM folder size --- Detect-IntuneCache.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Detect-IntuneCache.ps1 b/Detect-IntuneCache.ps1 index b917322..c8bb9a4 100644 --- a/Detect-IntuneCache.ps1 +++ b/Detect-IntuneCache.ps1 @@ -1,8 +1,9 @@ # Detection Script +$folderPath = 'C:\Windows\System32\config\systemprofile\AppData\Local\mdm' +$folderSize = (Get-ChildItem -Path $folderPath -Recurse -File | Measure-Object -Property Length -Sum).Sum +$maxSize = 100MB -$msiFiles = Get-ChildItem -Path 'C:\Windows\System32\config\systemprofile\AppData\Local\mdm' -Filter *.msi -File -ErrorAction SilentlyContinue - -if ($msiFiles.Count -gt 0) { +if ($folderSize -gt $maxSize) { write-host "Remediation needed" -foregroundcolor red exit 1 # Remediation needed } else { From a9106b4bf06159f25dd107dcc3a8e51dc26d36a8 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:38:18 -0700 Subject: [PATCH 028/107] Add files via upload Manage application updates/upgrades through Intune remediation scripts. --- Detect-WingetUpdates.ps1 | 34 ++++++++++++++++++++++++++++++++++ Remediate-WingetUpdates.ps1 | 28 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Detect-WingetUpdates.ps1 create mode 100644 Remediate-WingetUpdates.ps1 diff --git a/Detect-WingetUpdates.ps1 b/Detect-WingetUpdates.ps1 new file mode 100644 index 0000000..dd89304 --- /dev/null +++ b/Detect-WingetUpdates.ps1 @@ -0,0 +1,34 @@ +# Paths to check for the winget executable +$possibleWingetPaths = @( + "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe", + "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe", + "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" +) + +# Function to check if winget is installed +function Get-WingetPath { + foreach ($path in $possibleWingetPaths) { + $resolvedPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue + if ($resolvedPaths) { + return $resolvedPaths.FullName + } + } + return $null +} + +$wingetPath = Get-WingetPath + +if ($null -eq $wingetPath) { + Write-Error "Winget is not installed or the path is incorrect." + exit 1 +} + +# Check if there are updates available +$updates = & $wingetPath list --upgrade +if ($updates) { + Write-Output "Updates available" + exit 1 +} else { + Write-Output "No updates available" + exit 0 +} diff --git a/Remediate-WingetUpdates.ps1 b/Remediate-WingetUpdates.ps1 new file mode 100644 index 0000000..6679d54 --- /dev/null +++ b/Remediate-WingetUpdates.ps1 @@ -0,0 +1,28 @@ +# Paths to check for the winget executable +$possibleWingetPaths = @( + "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe", + "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe", + "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" +) + +# Function to check if winget is installed +function Get-WingetPath { + foreach ($path in $possibleWingetPaths) { + $resolvedPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue + if ($resolvedPaths) { + return $resolvedPaths.FullName + } + } + return $null +} + +$wingetPath = Get-WingetPath + +if ($null -eq $wingetPath) { + Write-Error "Winget is not installed or the path is incorrect." + exit 1 +} + +# Upgrade all winget-installed software +& $wingetPath upgrade --all --silent +Write-Output "Updates installed" From 68405eda4b8d6674052a9f38321154aaac352631 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:15:32 -0700 Subject: [PATCH 029/107] Add files via upload Enroll a device to Intune that previously failed --- Enroll-Intune.ps1 | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Enroll-Intune.ps1 diff --git a/Enroll-Intune.ps1 b/Enroll-Intune.ps1 new file mode 100644 index 0000000..7a40b97 --- /dev/null +++ b/Enroll-Intune.ps1 @@ -0,0 +1,52 @@ +# Function to get Tenant ID +function Get-TenantID { + param ( + [string]$KeyPath + ) + + try { + $keyinfo = Get-Item "HKLM:\$KeyPath" + return $keyinfo.name.Split("\")[-1] + } catch { + Write-Error "Tenant ID is not found!" + exit 1001 + } +} + +# Function to set MDM Enrollment URLs +function Set-MDMEnrollmentURLs { + param ( + [string]$Path + ) + + if (!(Test-Path $Path)) { + Write-Error "KEY $Path not found!" + exit 1002 + } else { + try { + Get-ItemProperty $Path -Name MdmEnrollmentUrl + } catch { + Write-Host "MDM Enrollment registry keys not found. Registering now..." + New-ItemProperty -LiteralPath $Path -Name 'MdmEnrollmentUrl' -Value 'https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc' -PropertyType String -Force -ErrorAction SilentlyContinue + New-ItemProperty -LiteralPath $Path -Name 'MdmTermsOfUseUrl' -Value 'https://portal.manage.microsoft.com/TermsofUse.aspx' -PropertyType String -Force -ErrorAction SilentlyContinue + New-ItemProperty -LiteralPath $Path -Name 'MdmComplianceUrl' -Value 'https://portal.manage.microsoft.com/?portalAction=Compliance' -PropertyType String -Force -ErrorAction SilentlyContinue + } finally { + # Trigger AutoEnroll with the deviceenroller + try { + & C:\Windows\system32\deviceenroller.exe /c /AutoEnrollMDM + Write-Host "Device is performing the MDM enrollment!" + exit 0 + } catch { + Write-Error "Something went wrong (C:\Windows\system32\deviceenroller.exe)" + exit 1003 + } + } + } +} + +# Main script logic +$key = 'SYSTEM\CurrentControlSet\Control\CloudDomainJoin\TenantInfo\*' +$tenantID = Get-TenantID -KeyPath $key +$path = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\TenantInfo\$tenantID" +Set-MDMEnrollmentURLs -Path $path +exit 0 From 001015bdbae58be30a836cd2f4a8c95be227cd2d Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:56:05 -0400 Subject: [PATCH 030/107] Add files via upload Working on porting so Graph SDK --- Intune-AutomationRunbookv2_1.ps1 | 3427 ++++++++++++++++++++++++++++++ 1 file changed, 3427 insertions(+) create mode 100644 Intune-AutomationRunbookv2_1.ps1 diff --git a/Intune-AutomationRunbookv2_1.ps1 b/Intune-AutomationRunbookv2_1.ps1 new file mode 100644 index 0000000..f0edbde --- /dev/null +++ b/Intune-AutomationRunbookv2_1.ps1 @@ -0,0 +1,3427 @@ +<# +.COPYRIGHT +Original Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +Updates made to modernize script and add other customizations. +See LICENSE in the project root for license information. +#> + +<# +.SYNOPSIS +After you run this script, you will have + +1.White labeled Terms and Conditions +2.A device compliance policy for: Windows10, Windows 11, iOS/ipad, Android for Work, Android Device Owner, macOS +3.A Windows 10 Endpoint Protection Profile for Windows Devices for Silent Bitlocker Encryption +4.M365 Apps to Windows 10 and MacOS +5.Office Suite for iOS and Android (Word, PowerPoint, Excel, Teams, OneDrive, Outlook) +6.Microsoft Authenticator pushed out as a required App for iOS and Android devices +7.App Protection Policies for Android and iOS without enrollment +8.Windows Information Protection policies with or without enrollment (2 policies) +9.Windows Update Rings for Windows 10 and Windows 11 + +#> + +#################################################### + +function Get-AuthToken { + param ( + [Parameter(Mandatory = $true)] + [string] $User + ) + + $userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User + $tenant = $userUpn.Host + + # Connect to Microsoft Graph + Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All" -ErrorAction Stop + if ($null -eq (Get-MgUser -Top 1 -ErrorAction SilentlyContinue)) { + throw "Failed to connect to Microsoft Graph." + } + Write-Host "Connected to Microsoft Graph" +} + +#################################################### + +# Function to handle errors from Microsoft Graph +function Handle-GraphError { + param ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.ErrorRecord] $ErrorRecord + ) + + if ($null -ne $ErrorRecord.Exception -and $null -ne $ErrorRecord.Exception.Response) { + try { + $errorResponse = $ErrorRecord.Exception.Response.GetResponseStream() + if ($null -ne $errorResponse) { + $reader = New-Object System.IO.StreamReader($errorResponse) + $responseBody = $reader.ReadToEnd() + Write-Host "Error response: $responseBody" + } else { + Write-Host "Error response stream is null." + } + } catch { + Write-Host "Error handling failed: $_" + } + } else { + Write-Host "No response available for this error." + } +} + +#################################################### + +function Validate-AADGroup { + param ( + [Parameter(Mandatory = $true)] + [string] $GroupName + ) + + $group = Get-MgGroup -Filter "displayName eq '$GroupName'" -ErrorAction SilentlyContinue + if ($null -eq $group) { + throw "AAD Group - '$GroupName' doesn't exist, please specify a valid AAD Group." + } + return $group +} + + +#################################################### + + +Function Test-JSON(){ + +<# +.SYNOPSIS +This function is used to test if the JSON passed to a REST Post request is valid +.DESCRIPTION +The function tests if the JSON passed to the REST Post is valid +.EXAMPLE +Test-JSON -JSON $JSON +Test if the JSON is valid before calling the Graph REST interface +.NOTES +NAME: Test-JSON +#> + +param ( + +$JSON + +) + + try { + + $TestJSON = ConvertFrom-Json $JSON -ErrorAction Stop + $validJson = $true + + } + + catch { + + $validJson = $false + $_.Exception + + } + + if (!$validJson){ + + Write-Host "Provided JSON isn't in valid JSON format" -f Red + break + + } + +} + +#################################################### + +Function Get-itunesApplication(){ + +<# +.SYNOPSIS +This function is used to get an iOS application from the itunes store using the Apple REST API interface +.DESCRIPTION +The function connects to the Apple REST API Interface and returns applications from the itunes store +.EXAMPLE +Get-itunesApplication -SearchString "Microsoft Corporation" +Gets an iOS application from itunes store +.EXAMPLE +Get-itunesApplication -SearchString "Microsoft Corporation" -Limit 10 +Gets an iOS application from itunes store with a limit of 10 results +.NOTES +NAME: Get-itunesApplication +#> + +[cmdletbinding()] + +param +( + [Parameter(Mandatory=$true)] + $SearchString, + [int]$Limit +) + + try{ + + Write-Verbose $SearchString + + # Testing if string contains a space and replacing it with a + + $SearchString = $SearchString.replace(" ","+") + + Write-Verbose "SearchString variable converted if there is a space in the name $SearchString" + + if($Limit){ + + $iTunesUrl = "https://itunes.apple.com/search?country=us&media=software&entity=software,iPadSoftware&term=$SearchString&limit=$limit" + + } + + else { + + $iTunesUrl = "https://itunes.apple.com/search?entity=software&term=$SearchString&attribute=softwareDeveloper" + + } + + write-verbose $iTunesUrl + $apps = Invoke-RestMethod -Uri $iTunesUrl -Method Get + + # Putting sleep in so that no more than 20 API calls to itunes API + sleep 3 + + return $apps + + } + + catch { + + write-host $_.Exception.Message -f Red + write-host $_.Exception.ItemName -f Red + write-verbose $_.Exception + write-host + break + + } + +} + +#################################################### +Function Add-iOSApplication(){ + +<# +.SYNOPSIS +This function is used to add an iOS application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an iOS application from the itunes store +.EXAMPLE +Add-iOSApplication -AuthHeader $AuthHeader +Adds an iOS application into Intune from itunes store +.NOTES +NAME: Add-iOSApplication +#> + +[cmdletbinding()] + +param +( + $itunesApp +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps" + + try { + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + + $app = $itunesApp + + Write-Verbose $app + + Write-Host "Publishing $($app.trackName)" -f Yellow + + # Step 1 - Downloading the icon for the application + $iconUrl = $app.artworkUrl60 + + if ($iconUrl -eq $null){ + + Write-Host "60x60 icon not found, using 100x100 icon" + $iconUrl = $app.artworkUrl100 + + } + + if ($iconUrl -eq $null){ + + Write-Host "60x60 icon not found, using 512x512 icon" + $iconUrl = $app.artworkUrl512 + + } + + $iconResponse = Invoke-WebRequest $iconUrl + $base64icon = [System.Convert]::ToBase64String($iconResponse.Content) + $iconType = $iconResponse.Headers["Content-Type"] + + if(($app.minimumOsVersion.Split(".")).Count -gt 2){ + + $Split = $app.minimumOsVersion.Split(".") + + $MOV = $Split[0] + "." + $Split[1] + + $osVersion = [Convert]::ToDouble($MOV) + + } + + else { + + $osVersion = [Convert]::ToDouble($app.minimumOsVersion) + + } + + # Setting support Operating System Devices + if($app.supportedDevices -match "iPadMini"){ $iPad = $true } else { $iPad = $false } + if($app.supportedDevices -match "iPhone6"){ $iPhone = $true } else { $iPhone = $false } + + # Step 2 - Create the Hashtable Object of the application + + $description = $app.description -replace "[^\x00-\x7F]+","" + + $graphApp = @{ + "@odata.type"="#microsoft.graph.iosStoreApp"; + displayName=$app.trackName; + publisher=$app.artistName; + description=$description; + largeIcon= @{ + type=$iconType; + value=$base64icon; + }; + isFeatured=$false; + appStoreUrl=$app.trackViewUrl; + applicableDeviceType=@{ + iPad=$iPad; + iPhoneAndIPod=$iPhone; + }; + minimumSupportedOperatingSystem=@{ + v8_0=$osVersion -lt 9.0; + v9_0=$osVersion -eq 9.0; + v10_0=$osVersion -gt 9.0; + }; + }; + + $JSON = ConvertTo-Json $graphApp + + # Step 3 - Publish the application to Graph + Write-Host "Creating application via Graph" + $createResult = Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body (ConvertTo-Json $graphApp) -Headers $authToken + Write-Host "Application created as $uri/$($createResult.id)" + write-host + + return $createResult + + } + + catch { + + $ex = $_.Exception + Write-Host "Request to $Uri failed with HTTP Status $([int]$ex.Response.StatusCode) $($ex.Response.StatusDescription)" -f Red + + $errorResponse = $ex.Response.GetResponseStream() + + $ex.Response.GetResponseStream() + + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +################################################################### + +function Add-TermsAndConditions { + param ( + [Parameter(Mandatory = $true)] + [string] $jsonContent + ) + + try { + Write-Host "Starting Add-TermsAndConditions function" + $terms = $jsonContent | ConvertFrom-Json + + # Log the converted terms object + Write-Host "Converted JSON Content: " -ForegroundColor Yellow + $terms | ConvertTo-Json -Depth 3 | Write-Host + + $url = "https://graph.microsoft.com/beta/deviceManagement/termsAndConditions" + + # Convert back to JSON string + $termsJson = $terms | ConvertTo-Json -Depth 3 + $response = Invoke-MgGraphRequest -Uri $url -Method POST -Body $termsJson -ContentType "application/json" + + # Log the response + Write-Host "Response: " -ForegroundColor Green + $response | ConvertTo-Json -Depth 3 | Write-Host + + return $response + } catch { + Write-Host "Error: $_" + } +} + + +#################################################### + +function Add-DeviceCompliancePolicy { + param ( + [Parameter(Mandatory = $true)] + [string] $jsonContent + ) + + try { + Write-Host "Creating Device Compliance Policy from JSON content..." + + # Convert JSON content to a PowerShell object + $policyData = $jsonContent | ConvertFrom-Json + + # Log the converted JSON object for debugging + Write-Host "Converted JSON Content: " -ForegroundColor Yellow + $policyData | ConvertTo-Json -Depth 3 | Write-Host + + # Create the device compliance policy using the New-MgDeviceManagementDeviceCompliancePolicy cmdlet + $policy = New-MgDeviceManagementDeviceCompliancePolicy -BodyParameter $policyData + + Write-Host "Device Compliance Policy created: $($policy.displayName)" + return $policy + } catch { + Write-Host "Error: $_" + } +} + + +########################################################################### + +Function Add-MDMApplication(){ + +<# +.SYNOPSIS +This function is used to add an MDM application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an MDM application from the itunes store +.EXAMPLE +Add-MDMApplication -JSON $JSON1 +Adds an application into Intune +.NOTES +NAME: Add-MDMApplication +#> + +[cmdletbinding()] + +param +( + $JSON1 +) + +$graphApiVersion = "Beta" +$App_resource = "deviceAppManagement/mobileApps" + + try { + + if(!$JSON1){ + + write-host "No JSON was passed to the function, provide a JSON variable" -f Red + break + + } + + Test-JSON -JSON $JSON1 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON1 -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON1 = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON1 -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +################################################################################### + +function Add-DeviceCompliancePolicy { + param ( + [Parameter(Mandatory = $true)] + [string] $jsonContent + ) + + try { + Write-Host "Creating Device Compliance Policy from JSON content..." + + # Convert JSON content to a PowerShell object + $policyData = $jsonContent | ConvertFrom-Json + + # Log the converted JSON object for debugging + Write-Host "Converted JSON Content: " -ForegroundColor Yellow + $policyData | ConvertTo-Json -Depth 5 | Write-Host + + # Create the device compliance policy using the New-MgDeviceManagementDeviceCompliancePolicy cmdlet + $policy = New-MgDeviceManagementDeviceCompliancePolicy -BodyParameter $policyData + + Write-Host "Device Compliance Policy created: $($policy.displayName)" + return $policy + } catch { + Write-Host "Error: $_" + } +} + +# Define the JSON content +$JSON_macOS = @" +{ + "@odata.type": "#microsoft.graph.macOSCompliancePolicy", + "displayName": "macOS_Compliance", + "description": "1. Require system integrity protection\n2. Minimum OS version: 10.13\n3. Require a password to unlock devices.\n4. Block Simple passwords\n5. Minimum password length: 8 Alphanumeric\n6. Number of non-alphanumeric characters in password: 1\n7. Maximum minutes of inactivity before password is required: 5 Minutes\n8. Password expiration (days): 90\n9. Number of previous passwords to prevent reuse: 5\n10. Require encryption of data storage on device.\n11. Enable Firewall\n12. Block Incoming connections\n13. Allow apps downloaded from these locations: Mac App Store", + "passwordRequired": true, + "passwordBlockSimple": true, + "passwordExpirationDays": 90, + "passwordMinimumLength": 8, + "passwordMinutesOfInactivityBeforeLock": 5, + "passwordPreviousPasswordBlockCount": 5, + "passwordMinimumCharacterSetCount": 1, + "passwordRequiredType": "alphanumeric", + "osMinimumVersion": "10.13", + "osMaximumVersion": "10.15", + "systemIntegrityProtectionEnabled": true, + "deviceThreatProtectionEnabled": false, + "storageRequireEncryption": true, + "firewallEnabled": true, + "firewallBlockAllIncoming": true, + "firewallEnableStealthMode": false, + "scheduledActionsForRule": [ + { + "ruleName": "PasswordRequired", + "scheduledActionConfigurations": [ + { + "actionType": "block", + "gracePeriodHours": 0, + "notificationTemplateId": null + } + ] + } + ] +} +"@ + +# Call the function with the correct parameter +$CreateResult_macOS = Add-DeviceCompliancePolicy -jsonContent $JSON_macOS + + +#################################################### + +Function Add-DeviceCompliancePolicyAssignment(){ + +<# +.SYNOPSIS +This function is used to add a device compliance policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device compliance policy assignment +.EXAMPLE +Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CompliancePolicyId -TargetGroupId $TargetGroupId +Adds a device compliance policy assignment in Intune +.NOTES +NAME: Add-DeviceCompliancePolicyAssignment +#> + +[cmdletbinding()] + +param +( + $CompliancePolicyId, + $TargetGroupId +) + +$graphApiVersion = "beta" +$Resource = "deviceManagement/deviceCompliancePolicies/$CompliancePolicyId/assign" + + try { + + if(!$CompliancePolicyId){ + + write-host "No Compliance Policy Id specified, specify a valid Compliance Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + +$JSON = @" + { + "assignments": [ + { + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + } + } + ] + } + +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + + +#################################################### + +Function Add-DeviceConfigurationPolicy(){ + +<# +.SYNOPSIS +This function is used to add an device configuration policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device configuration policy +.EXAMPLE +Add-DeviceConfigurationPolicy -JSON $JSON +Adds a device configuration policy in Intune +.NOTES +NAME: Add-DeviceConfigurationPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$DCP_resource = "deviceManagement/deviceConfigurations" +Write-Verbose "Resource: $DCP_resource" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### +Function Add-WindowsUpdatePolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows update (ring) policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an update ring configuration policy +.EXAMPLE +Add-WindowsUpdatePolicy -JSON $JSON +Adds a device update ring policy in Intune +.NOTES +NAME: Add-WindowsUpdatePolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "v1.0" +$DCP_resource = "/deviceManagement/deviceConfigurations" +Write-Verbose "Resource: $DCP_resource" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Windows Update Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### +Function Add-AndroidApplication(){ + +<# +.SYNOPSIS +This function is used to add an Android application using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds an Android application from the itunes store +.EXAMPLE +Add-AndroidApplication -JSON $JSON -IconURL pathtourl +Adds an Android application into Intune using an icon from a URL +.NOTES +NAME: Add-AndroidApplication +#> + +[cmdletbinding()] + +param +( + $JSON3, + $IconURL +) + +$graphApiVersion = "Beta" +$App_resource = "deviceAppManagement/mobileApps" + + try { + + if(!$JSON3){ + + write-host "No JSON was passed to the function, provide a JSON variable" -f Red + break + + } + + + if($IconURL){ + + write-verbose "Icon specified: $IconURL" + + if(!(test-path "$IconURL")){ + + write-host "Icon Path '$IconURL' doesn't exist..." -ForegroundColor Red + Write-Host "Please specify a valid path..." -ForegroundColor Red + Write-Host + break + + } + + $iconResponse = Invoke-WebRequest "$iconUrl" + $base64icon = [System.Convert]::ToBase64String($iconResponse.Content) + $iconExt = ([System.IO.Path]::GetExtension("$iconURL")).replace(".","") + $iconType = "image/$iconExt" + + Write-Verbose "Updating JSON to add Icon Data" + + $U_JSON3 = ConvertFrom-Json $JSON3 + + $U_JSON3.largeIcon.type = "$iconType" + $U_JSON3.largeIcon.value = "$base64icon" + + $JSON3 = ConvertTo-Json $U_JSON3 + + Write-Verbose $JSON3 + + Test-JSON -JSON $JSON3 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON3 -Headers $authToken + + } + + else { + + Test-JSON -JSON $JSON3 + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($App_resource)" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON3 -Headers $authToken + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +######################################################### + +Function Add-DeviceConfigurationPolicyAssignment(){ + +<# +.SYNOPSIS +This function is used to add a device configuration policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a device configuration policy assignment +.EXAMPLE +Add-DeviceConfigurationPolicyAssignment -ConfigurationPolicyId $ConfigurationPolicyId -TargetGroupId $TargetGroupId +Adds a device configuration policy assignment in Intune +.NOTES +NAME: Add-DeviceConfigurationPolicyAssignment +#> + +[cmdletbinding()] + +param +( + $ConfigurationPolicyId, + $TargetGroupId +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/deviceConfigurations/$ConfigurationPolicyId/assign" + + try { + + if(!$ConfigurationPolicyId){ + + write-host "No Configuration Policy Id specified, specify a valid Configuration Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + $ConfPolAssign = "$ConfigurationPolicyId" + "_" + "$TargetGroupId" + +$JSON = @" +{ + "deviceConfigurationGroupAssignments": [ + { + "@odata.type": "#microsoft.graph.deviceConfigurationGroupAssignment", + "id": "$ConfPolAssign", + "targetGroupId": "$TargetGroupId" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +######################################################### +<# +Function Add-WindowsUpdatePolicyAssignment(){ +#> +<# +.SYNOPSIS +This function is used to add a Windows Update configuration policy assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Update configuration policy assignment +.EXAMPLE +Add-WindowsUpdatePolicyAssignment -ConfigurationPolicyId $ConfigurationPolicyId -TargetGroupId $TargetGroupId +Adds a device Windows Update policy assignment in Intune +.NOTES +NAME: Add-WindowsUpdatePolicyAssignment +#> +<# +[cmdletbinding()] + +param +( + $ConfigurationPolicyId, + $TargetGroupId +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/deviceConfigurations/$ConfigurationPolicyId/assign" + + try { + + if(!$ConfigurationPolicyId){ + + write-host "No Configuration Policy Id specified, specify a valid Configuration Policy Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + $ConfPolAssign = "$ConfigurationPolicyId" + "_" + "$TargetGroupId" + +$JSON = @" +{ + "deviceConfigurationGroupAssignments": [ + { + "@odata.type": "#microsoft.graph.deviceConfigurationGroupAssignment", + "id": "$ConfPolAssign", + "targetGroupId": "$TargetGroupId" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +#> +############################################################################# + +Function Add-ApplicationAssignment(){ + +<# +.SYNOPSIS +This function is used to add an application assignment using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a application assignment +.EXAMPLE +Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent $InstallIntent +Adds an application assignment in Intune +.NOTES +NAME: Add-ApplicationAssignment +#> + +[cmdletbinding()] + +param +( + $ApplicationId, + $TargetGroupId, + $InstallIntent +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mobileApps/$ApplicationId/assign" + + try { + + if(!$ApplicationId){ + + write-host "No Application Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + + if(!$InstallIntent){ + + write-host "No Install Intent specified, specify a valid Install Intent - available, notApplicable, required, uninstall, availableWithoutEnrollment" -f Red + break + + } + +$JSON4 = @" +{ + "mobileAppAssignments": [ + { + "@odata.type": "#microsoft.graph.mobileAppAssignment", + "target": { + "@odata.type": "#microsoft.graph.groupAssignmentTarget", + "groupId": "$TargetGroupId" + }, + "intent": "$InstallIntent" + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON4 -ContentType "application/json" + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +########################################################################################## + +Function Add-ManagedAppPolicy(){ + +<# +.SYNOPSIS +This function is used to add an Managed App policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Managed App policy +.EXAMPLE +Add-ManagedAppPolicy -JSON $JSON +Adds a Managed App policy in Intune +.NOTES +NAME: Add-ManagedAppPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/managedAppPolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for a Managed App Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + Write-Host + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-ManagedAppPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a Managed App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a Managed App Policy with an AAD Group +.EXAMPLE +Assign-ManagedAppPolicy -Id $Id -TargetGroupId $TargetGroupId -OS Android +Assigns an AAD Group assignment to an Android App Protection Policy in Intune +.EXAMPLE +Assign-ManagedAppPolicy -Id $Id -TargetGroupId $TargetGroupId -OS iOS +Assigns an AAD Group assignment to an iOS App Protection Policy in Intune +.NOTES +NAME: Assign-ManagedAppPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId, + $OS +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + +$JSON = @" +{ + "assignments":[ + { + "target": + { + "groupId":"$TargetGroupId", + "@odata.type":"#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + if($OS -eq "" -or $OS -eq $null){ + + write-host "No OS parameter specified, please provide an OS. Supported value Android or iOS..." -f Red + Write-Host + break + + } + + elseif($OS -eq "Android"){ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/iosManagedAppProtections('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + elseif($OS -eq "iOS"){ + + $uri = "https://graph.microsoft.com/$graphApiVersion/deviceAppManagement/iosManagedAppProtections('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +###################################################### + +Function Add-WindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows Information Protection policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Information Protection policy +.EXAMPLE +Add-WindowsInformationProtectionPolicy -JSON $JSON +Adds a Windows Information Protection policy in Intune +.NOTES +NAME: Add-WindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/windowsManagedAppProtections" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the iOS Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + write-host "Adding JSON information to Windows Information Protection Policy: $JSON" + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} +###################################################### + +Function Add-WindowsBaselineSecurity(){ + +<# +.SYNOPSIS +This function is used to add a Windows Baseline Security policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Baseline Security policy +.EXAMPLE +Add-WindowsBaselineSecurity -JSON $JSON +Adds a Windows Baseline Security policy in Intune +.NOTES +NAME: Add-WindowsBaselineSecurity +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceManagement/intents" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the Baseline Security Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Add-MDMWindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to add a Windows Information Protection policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and adds a Windows Information Protection policy +.EXAMPLE +Add-MDMWindowsInformationProtectionPolicy -JSON $JSON +Adds a Windows Information Protection policy in Intune +.NOTES +NAME: Add-MDMWindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $JSON +) + +$graphApiVersion = "Beta" +$Resource = "deviceAppManagement/mdmWindowsInformationProtectionPolicies" + + try { + + if($JSON -eq "" -or $JSON -eq $null){ + + write-host "No JSON specified, please specify valid JSON for the iOS Policy..." -f Red + + } + + else { + + Test-JSON -JSON $JSON + + $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" + Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" + + } + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-WindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a WIP App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a WIP App Policy with an AAD Group +.EXAMPLE +Assign-WindowsInformationProtectionPolicy -Id $Id -TargetGroupId $TargetGroupId +Assigns an AAD Group assignment to a WIP App Protection Policy in Intune +.NOTES +NAME: Assign-WindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + +$JSON = @" +{ + "assignments": [ + { + "target": { + "groupId": "$TargetGroupId", + "@odata.type": "#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/windowsInformationProtectionPolicies('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +Function Assign-MDMWindowsInformationProtectionPolicy(){ + +<# +.SYNOPSIS +This function is used to assign an AAD group to a WIP App Policy using the Graph API REST interface +.DESCRIPTION +The function connects to the Graph API Interface and assigns a WIP App Policy with an AAD Group +.EXAMPLE +Assign-MDMWindowsInformationProtectionPolicy -Id $Id -TargetGroupId $TargetGroupId +Assigns an AAD Group assignment to a WIP App Protection Policy in Intune +.NOTES +NAME: Assign-MDMWindowsInformationProtectionPolicy +#> + +[cmdletbinding()] + +param +( + $Id, + $TargetGroupId +) + +$graphApiVersion = "Beta" + + try { + + if(!$Id){ + + write-host "No Policy Id specified, specify a valid Application Id" -f Red + break + + } + + if(!$TargetGroupId){ + + write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red + break + + } + + +$JSON = @" +{ + "assignments": [ + { + "target": { + "groupId": "$TargetGroupId", + "@odata.type": "#microsoft.graph.groupAssignmentTarget" + } + } + ] +} +"@ + + $uri = "https://graph.microsoft.com/beta/deviceAppManagement/mdmWindowsInformationProtectionPolicies('$ID')/assign" + Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $JSON -Headers $authToken + + } + + catch { + + $ex = $_.Exception + $errorResponse = $ex.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($errorResponse) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "Response content:`n$responseBody" -f Red + Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" + write-host + break + + } + +} + +#################################################### + +function Get-EnterpriseDomain { + param ( + [Parameter(Mandatory = $true)] + [string] $domainName + ) + + write-host "Calling Get-EnterpriseDomain function" -ForegroundColor cyan + try { + $domain = Get-MgDomain -Filter "id eq '$domainName'" -ErrorAction Stop + if ($null -eq $domain) { + throw "Domain '$domainName' not found." + } + return $domain + } catch { + Write-Host "Error: $_" + } +} + + +#################################################### + +function Get-TenantName { + write-host "Calling Get-TenantName function" -ForegroundColor cyan + try { + $organization = Get-MgOrganization + return $organization.DisplayName + } catch { + Write-Host "Error: $_" + } +} + + + +############################################ + +function Get-AADGroup { + param ( + [Parameter(Mandatory = $true)] + [string] $groupName + ) + + try { + $group = Get-MgGroup -Filter "displayName eq '$groupName'" -ErrorAction Stop + if ($null -eq $group) { + throw "Group '$groupName' not found." + } + return $group + } catch { + Write-Host "Error: $_" + } +} + + +#################################################### + +#region Authentication + +write-host + +# Checking if authToken exists before running authentication +if($global:authToken){ + + # Setting DateTime to Universal time to work in all timezones + $DateTime = (Get-Date).ToUniversalTime() + + # If the authToken exists checking when it expires + $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes + + if($TokenExpires -le 0){ + + write-host "Authentication Token expired" $TokenExpires "minutes ago" -ForegroundColor Yellow + write-host + + # Defining User Principal Name if not present + + if($User -eq $null -or $User -eq ""){ + + $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication" + Write-Host + + } + + $global:authToken = Get-AuthToken -User $User + + } +} + +# Authentication doesn't exist, calling Get-AuthToken function + +else { + + if($User -eq $null -or $User -eq ""){ + + $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication" + Write-Host + + } + +# Getting the authorization token +$global:authToken = Get-AuthToken -User $User + +} + +#endregion + +#################################################### +<# Configurations using JSON Files #> +#################################################### + +$JSON_10QualityUpdate = @" +{ + "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", + "supportsScopeTags":true, + "roleScopeTagIds":["0"], + "deviceManagementApplicabilityRuleOsEdition":null, + "deviceManagementApplicabilityRuleOsVersion":null, + "deviceManagementApplicabilityRuleDeviceMode":null, + "description":"", + "displayName":"Windows 10 Update Ring", + "version":1, + "deliveryOptimizationMode":"userDefined", + "prereleaseFeatures":"userDefined", + "automaticUpdateMode":"autoInstallAndRebootAtMaintenanceTime", + "microsoftUpdateServiceAllowed":true, + "driversExcluded":false, + "qualityUpdatesDeferralPeriodInDays":0, + "featureUpdatesDeferralPeriodInDays":0, + "qualityUpdatesPaused":false, + "featureUpdatesPaused":false, + "qualityUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "businessReadyUpdatesOnly":"userDefined", + "skipChecksBeforeRestart":false, + "updateWeeks":null, + "qualityUpdatesPauseStartDate":null, + "featureUpdatesPauseStartDate":null, + "featureUpdatesRollbackWindowInDays":60, + "qualityUpdatesWillBeRolledBack":null, + "featureUpdatesWillBeRolledBack":null, + "qualityUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "engagedRestartDeadlineInDays":null, + "engagedRestartSnoozeScheduleInDays":null, + "engagedRestartTransitionScheduleInDays":null, + "deadlineForFeatureUpdatesInDays":15, + "deadlineForQualityUpdatesInDays":5, + "deadlineGracePeriodInDays":5, + "postponeRebootUntilAfterDeadline":false, + "autoRestartNotificationDismissal":"notConfigured", + "scheduleRestartWarningInHours":null, + "scheduleImminentRestartWarningInMinutes":null, + "userPauseAccess":"disabled", + "userWindowsUpdateScanAccess":"enabled", + "updateNotificationLevel":"restartWarningsOnly", + "allowWindows11Upgrade":false, + "installationSchedule":{ + "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", + "activeHoursStart":"07:00:00.0000000", + "activeHoursEnd":"19:00:00.0000000" + } +} +"@ + +#################################################### +$JSON_11QualityUpdate = @" +{ + "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", + "supportsScopeTags":true, + "roleScopeTagIds":["0"], + "deviceManagementApplicabilityRuleOsEdition":null, + "deviceManagementApplicabilityRuleOsVersion":null, + "deviceManagementApplicabilityRuleDeviceMode":null, + "description":"", + "displayName":"Windows 11 Update Ring", + "version":1, + "deliveryOptimizationMode":"userDefined", + "prereleaseFeatures":"userDefined", + "automaticUpdateMode":"autoInstallAndRebootAtMaintenanceTime", + "microsoftUpdateServiceAllowed":true, + "driversExcluded":false, + "qualityUpdatesDeferralPeriodInDays":0, + "featureUpdatesDeferralPeriodInDays":0, + "qualityUpdatesPaused":false, + "featureUpdatesPaused":false, + "qualityUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesPauseExpiryDateTime":"0001-01-01T00:00:00Z", + "businessReadyUpdatesOnly":"userDefined", + "skipChecksBeforeRestart":false, + "updateWeeks":null, + "qualityUpdatesPauseStartDate":null, + "featureUpdatesPauseStartDate":null, + "featureUpdatesRollbackWindowInDays":60, + "qualityUpdatesWillBeRolledBack":null, + "featureUpdatesWillBeRolledBack":null, + "qualityUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "featureUpdatesRollbackStartDateTime":"0001-01-01T00:00:00Z", + "engagedRestartDeadlineInDays":null, + "engagedRestartSnoozeScheduleInDays":null, + "engagedRestartTransitionScheduleInDays":null, + "deadlineForFeatureUpdatesInDays":15, + "deadlineForQualityUpdatesInDays":5, + "deadlineGracePeriodInDays":5, + "postponeRebootUntilAfterDeadline":false, + "autoRestartNotificationDismissal":"notConfigured", + "scheduleRestartWarningInHours":null, + "scheduleImminentRestartWarningInMinutes":null, + "userPauseAccess":"disabled", + "userWindowsUpdateScanAccess":"enabled", + "updateNotificationLevel":"restartWarningsOnly", + "allowWindows11Upgrade":true, + "installationSchedule":{ + "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", + "activeHoursStart":"07:00:00.0000000", + "activeHoursEnd":"19:00:00.0000000" + } +} +"@ + +#################################################### + + +$JSON_AndroidWork = @" + { + "@odata.type": "#microsoft.graph.androidWorkProfileCompliancePolicy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "description": "1. Block Rooted devices\n2. Google Play Services is configured\n3. SafetyNet device attestation: Check basic integrity\n4. Require a password to unlock mobile devices.\n5. Required password type: Numeric complex\n6. Maximum minutes of inactivity before password is required: 1 Minute\n7. Minimum password length: 6 \n8. Require Encryption of data storage on device.\n9. Block apps from unknown sources\n10. Require Company Portal app runtime integrity\n11.Block USB debugging on device\n12. Minimum security patch level: 2020-01-01", + "displayName": "Android_Work_Device", + "passwordRequired": true, + "passwordMinimumLength": 6, + "passwordRequiredType": "numericComplex", + "passwordMinutesOfInactivityBeforeLock": 1, + "passwordExpirationDays": null, + "passwordPreviousPasswordBlockCount": null, + "passwordSignInFailureCountBeforeFactoryReset": null, + "securityPreventInstallAppsFromUnknownSources": true, + "securityDisableUsbDebugging": true, + "securityRequireVerifyApps": false, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "advancedThreatProtectionRequiredSecurityLevel": "unavailable", + "securityBlockJailbrokenDevices": true, + "osMinimumVersion": null, + "osMaximumVersion": null, + "minAndroidSecurityPatchLevel": "2020-01-01", + "storageRequireEncryption": true, + "securityRequireSafetyNetAttestationBasicIntegrity": true, + "securityRequireSafetyNetAttestationCertifiedDevice": false, + "securityRequireGooglePlayServices": true, + "securityRequireUpToDateSecurityProviders": false, + "securityRequireCompanyPortalAppIntegrity": true + + } +"@ + +#################################################### + +$JSON_AndroidOwner = @" + { + "@odata.type": "#microsoft.graph.androidDeviceOwnerCompliancePolicy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "description": "1. SafetyNet device attestation: Check basic integrity\n2. Require a password to unlock mobile devices.\n3. Minimum password length: 6 Numeric\n4. Maximum minutes of inactivity before password is required: 1 Minute\n5. Require Encryption of data storage on device.\n", + "displayName": "Android_Device_Owner", + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "advancedThreatProtectionRequiredSecurityLevel": "unavailable", + "securityRequireSafetyNetAttestationBasicIntegrity": true, + "securityRequireSafetyNetAttestationCertifiedDevice": false, + "osMinimumVersion": null, + "osMaximumVersion": null, + "minAndroidSecurityPatchLevel": null, + "passwordRequired": true, + "passwordMinimumLength": 6, + "passwordMinimumLetterCharacters": null, + "passwordMinimumLowerCaseCharacters": null, + "passwordMinimumNonLetterCharacters": null, + "passwordMinimumNumericCharacters": null, + "passwordMinimumSymbolCharacters": null, + "passwordMinimumUpperCaseCharacters": null, + "passwordRequiredType": "numeric", + "passwordMinutesOfInactivityBeforeLock": 1, + "passwordExpirationDays": null, + "passwordPreviousPasswordCountToBlock": null, + "storageRequireEncryption": true + + } +"@ + +#################################################### + +$JSON_iOS = @" + { + "@odata.type": "microsoft.graph.iosCompliancePolicy", + "description": "1. Block Jailbroken devices\n2. Require a password to unlock mobile devices.\n3. Block Simple passwords\n4. Minimum password length: 6 Numeric\n5. Maximum minutes after screen lock before password is required: Immediately\n6. Maximum minutes of inactivity until screen locks: 1", + "displayName": "iOS Compliance Policy", + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passcodeBlockSimple": true, + "passcodeExpirationDays": null, + "passcodeMinimumLength": 6, + "passcodeMinutesOfInactivityBeforeLock": 0, + "passcodeMinutesOfInactivityBeforeScreenTimeout": 1, + "passcodePreviousPasscodeBlockCount": null, + "passcodeMinimumCharacterSetCount": null, + "passcodeRequiredType": "numeric", + "passcodeRequired": true, + "securityBlockJailbrokenDevices": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "low" + } +"@ + +############################### + +$JSON_Windows10 = @" + { + "@odata.type": "#microsoft.graph.windows10CompliancePolicy", + "description": "1. Require BitLocker\n2. Require Secure Boot to be enabled on the device\n3. Require code integrity\n4. Require Encryption of data storage on device.\n5. Firewall, Antivirus, Antispyware", + "displayName": "Windows 10 Compliance Policy", + "version": 7, + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passwordRequired": false, + "passwordBlockSimple": false, + "passwordRequiredToUnlockFromIdle": false, + "passwordMinutesOfInactivityBeforeLock": null, + "passwordExpirationDays": null, + "passwordMinimumLength": null, + "passwordMinimumCharacterSetCount": null, + "passwordRequiredType": "deviceDefault", + "passwordPreviousPasswordBlockCount": null, + "requireHealthyDeviceReport": false, + "osMinimumVersion": "10.0.19045.4170", + "osMaximumVersion": null, + "mobileOsMinimumVersion": null, + "mobileOsMaximumVersion": null, + "earlyLaunchAntiMalwareDriverEnabled": false, + "bitLockerEnabled": true, + "secureBootEnabled": true, + "codeIntegrityEnabled": true, + "storageRequireEncryption": true, + "activeFirewallRequired": true, + "defenderEnabled": false, + "defenderVersion": null, + "signatureOutOfDate": false, + "rtpEnabled": false, + "antivirusRequired": true, + "antiSpywareRequired": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "configurationManagerComplianceRequired": false, + "tpmRequired": false, + "validOperatingSystemBuildRanges": [] +} +"@ + +$JSON_Windows11 = @" + { + "@odata.type": "#microsoft.graph.windows10CompliancePolicy", + "description": "1. Require BitLocker\n2. Require Secure Boot to be enabled on the device\n3. Require code integrity\n4. Require Encryption of data storage on device.\n5. Firewall, Antivirus, Antispyware", + "displayName": "Windows 11 Compliance Policy", + "version": 7, + "scheduledActionsForRule":[{"ruleName":"PasswordRequired","scheduledActionConfigurations":[{"actionType":"block","gracePeriodHours":0,"notificationTemplateId":""}]}], + "passwordRequired": false, + "passwordBlockSimple": false, + "passwordRequiredToUnlockFromIdle": false, + "passwordMinutesOfInactivityBeforeLock": null, + "passwordExpirationDays": null, + "passwordMinimumLength": null, + "passwordMinimumCharacterSetCount": null, + "passwordRequiredType": "deviceDefault", + "passwordPreviousPasswordBlockCount": null, + "requireHealthyDeviceReport": false, + "osMinimumVersion": "10.0.22631.3296", + "osMaximumVersion": null, + "mobileOsMinimumVersion": null, + "mobileOsMaximumVersion": null, + "earlyLaunchAntiMalwareDriverEnabled": false, + "bitLockerEnabled": true, + "secureBootEnabled": true, + "codeIntegrityEnabled": true, + "storageRequireEncryption": true, + "activeFirewallRequired": true, + "defenderEnabled": false, + "defenderVersion": null, + "signatureOutOfDate": false, + "rtpEnabled": false, + "antivirusRequired": true, + "antiSpywareRequired": true, + "deviceThreatProtectionEnabled": false, + "deviceThreatProtectionRequiredSecurityLevel": "unavailable", + "configurationManagerComplianceRequired": false, + "tpmRequired": false, + "validOperatingSystemBuildRanges": [] +} +"@ + + +$JSON = @" +{ + "@odata.type": "#microsoft.graph.termsAndConditions", + "displayName":"Customer Terms and Conditions", + "title":"Terms and Conditions", + "description":"By enrolling your device, you agree to $TenantName1 terms and conditions", + "bodyText":"I acknowledge that by enrolling my device, $TenantName1 Administrators have certain types of control. This includes visibility into corporate app inventory, email usage, and device risk. I further agree to keep company resources safe to the best of my ability and inform $TenantName1 administrators as soon as I believe my device is lost or stolen", + "acceptanceStatement":"I accept", + "version":1 +} +"@ + + +$JSON1 = @" +{ + "@odata.type": "#microsoft.graph.officeSuiteApp", + "autoAcceptEula": true, + "description":"Office 365 - Assigned", + "developer": "Microsoft", + "displayName":"Office 365 - Assigned", + "excludedApps": { + "groove": true, + "infoPath": true, + "sharePointDesigner": true + }, + "informationUrl": "", + "isFeatured": false, + "largeIcon": { + "type": "image/png", + "value": "iVBORw0KGgoAAAANSUhEUgAAAF0AAAAeCAMAAAEOZNKlAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJhUExURf////7z7/i9qfF1S/KCW/i+qv3q5P/9/PrQwfOMae1RG+s8AOxGDfBtQPWhhPvUx/759/zg1vWgg+9fLu5WIvKFX/rSxP728/nCr/FyR+tBBvOMaO1UH+1RHOs+AvSScP3u6f/+/v3s5vzg1+xFDO9kNPOOa/i7pvzj2/vWyes9Af76+Pzh2PrTxf/6+f7y7vOGYexHDv3t5+1SHfi8qPOIZPvb0O1NFuxDCe9hMPSVdPnFs/3q4/vaz/STcu5VIe5YJPWcfv718v/9/e1MFfF4T/F4TvF2TP3o4exECvF0SexIEPONavzn3/vZze1QGvF3Te5dK+5cKvrPwPrQwvKAWe1OGPexmexKEveulfezm/BxRfamiuxLE/apj/zf1e5YJfSXd/OHYv3r5feznPakiPze1P7x7f739f3w6+xJEfnEsvWdf/Wfge1LFPe1nu9iMvnDsfBqPOs/BPOIY/WZevJ/V/zl3fnIt/vTxuxHD+xEC+9mN+5ZJv749vBpO/KBWvBwRP/8+/SUc/etlPjArP/7+vOLZ/F7UvWae/708e1OF/aihvSWdvi8p+tABfSZefvVyPWihfSVde9lNvami+9jM/zi2fKEXvBuQvOKZvalifF5UPJ/WPSPbe9eLfrKuvvd0uxBB/7w7Pzj2vrRw/rOv+1PGfi/q/eymu5bKf3n4PnJuPBrPf3t6PWfgvWegOxCCO9nOO9oOfaskvSYePi5pPi2oPnGtO5eLPevlvKDXfrNvv739Pzd0/708O9gL+9lNfJ9VfrLu/OPbPnDsPBrPus+A/nArfarkQAAAGr5HKgAAADLdFJOU/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AvuakogAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAz5JREFUOE+tVTtu4zAQHQjppmWzwIJbEVCzpTpjbxD3grQHSOXKRXgCAT6EC7UBVAmp3KwBnmvfzNCyZTmxgeTZJsXx43B+HBHRE34ZkXgkerXFTheeiCkRrbB4UXmp4wSWz5raaQEMTM5TZwuiXoaKgV+6FsmkZQcSy0kA71yMTMGHanX+AzMMGLAQCxU1F/ZwjULPugazl82GM0NEKm/U8EqFwEkO3/EAT4grgl0nucwlk9pcpTTJ4VPA4g/Rb3yIRhhp507e9nTQmZ1OS5RO4sS7nIRPEeHXCHdkw9ZEW2yVE5oIS7peD58Avs7CN+PVCmHh21oOqBdjDzIs+FldPJ74TFESUSJEfVzy9U/dhu+AuOT6eBp6gGKyXEx8euO450ZE4CMfstMFT44broWw/itkYErWXRx+fFArt9Ca9os78TFed0LVIUsmIHrwbwaw3BEOnOk94qVpQ6Ka2HjxewJnfyd6jUtGDQLdWlzmYNYLeKbbGOucJsNabCq1Yub0o92rtR+i30V2dapxYVEePXcOjeCKPnYyit7BtKeNlZqHbr+gt7i+AChWA9RsRs03pxTQc67ouWpxyESvjK5Vs3DVSy3IpkxPm5X+wZoBi+MFHWW69/w8FRhc7VBe6HAhMB2b8Q0XqDzTNZtXUMnKMjwKVaCrB/CSUL7WSx/HsdJC86lFGXwnioTeOMPjV+szlFvrZLA5VMVK4y+41l4e1xfx7Z88o4hkilRUH/qKqwNVlgDgpvYCpH3XwAy5eMCRnezIUxffVXoDql2rTHFDO+pjWnTWzAfrYXn6BFECblUpWGrvPZvBipETjS5ydM7tdXpH41ZCEbBNy/+wFZu71QO2t9pgT+iZEf657Q1vpN94PQNDxUHeKR103LV9nPVOtDikcNKO+2naCw7yKBhOe9Hm79pe8C4/CfC2wDjXnqC94kEeBU3WwN7dt/2UScXas7zDl5GpkY+M8WKv2J7fd4Ib2rGTk+jsC2cleEM7jI9veF7B0MBJrsZqfKd/81q9pR2NZfwJK2JzsmIT1Ns8jUH0UusQBpU8d2JzsHiXg1zXGLqxfitUNTDT/nUUeqDBp2HZVr+Ocqi/Ty3Rf4Jn82xxfSNtAAAAAElFTkSuQmCC" + }, + "localesToInstall": [ + "en-us" + ], + "notes": "", + "officePlatformArchitecture": "x64", + "officeSuiteAppDefaultFileFormat":"officeOpenDocumentFormat", + "owner": "Microsoft", + "privacyInformationUrl": "", + "productIds": [ + "o365ProPlusRetail" + ], + "publisher": "Microsoft", + "updateChannel": "Current", + "useSharedComputerActivation": false +} +"@ + +$mac_OfficeApps = @" +{ + "@odata.type": "#microsoft.graph.macOSOfficeSuiteApp", + "description": "MacOS Office 365 - Assigned", + "developer": "Microsoft", + "displayName": "Mac Office 365 - Assigned", + "informationUrl": "", + "isFeatured": false, + "largeIcon": { + "type": "image/png", + "value": "iVBORw0KGgoAAAANSUhEUgAAAF0AAAAeCAMAAAEOZNKlAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJhUExURf////7z7/i9qfF1S/KCW/i+qv3q5P/9/PrQwfOMae1RG+s8AOxGDfBtQPWhhPvUx/759/zg1vWgg+9fLu5WIvKFX/rSxP728/nCr/FyR+tBBvOMaO1UH+1RHOs+AvSScP3u6f/+/v3s5vzg1+xFDO9kNPOOa/i7pvzj2/vWyes9Af76+Pzh2PrTxf/6+f7y7vOGYexHDv3t5+1SHfi8qPOIZPvb0O1NFuxDCe9hMPSVdPnFs/3q4/vaz/STcu5VIe5YJPWcfv718v/9/e1MFfF4T/F4TvF2TP3o4exECvF0SexIEPONavzn3/vZze1QGvF3Te5dK+5cKvrPwPrQwvKAWe1OGPexmexKEveulfezm/BxRfamiuxLE/apj/zf1e5YJfSXd/OHYv3r5feznPakiPze1P7x7f739f3w6+xJEfnEsvWdf/Wfge1LFPe1nu9iMvnDsfBqPOs/BPOIY/WZevJ/V/zl3fnIt/vTxuxHD+xEC+9mN+5ZJv749vBpO/KBWvBwRP/8+/SUc/etlPjArP/7+vOLZ/F7UvWae/708e1OF/aihvSWdvi8p+tABfSZefvVyPWihfSVde9lNvami+9jM/zi2fKEXvBuQvOKZvalifF5UPJ/WPSPbe9eLfrKuvvd0uxBB/7w7Pzj2vrRw/rOv+1PGfi/q/eymu5bKf3n4PnJuPBrPf3t6PWfgvWegOxCCO9nOO9oOfaskvSYePi5pPi2oPnGtO5eLPevlvKDXfrNvv739Pzd0/708O9gL+9lNfJ9VfrLu/OPbPnDsPBrPus+A/nArfarkQAAAGr5HKgAAADLdFJOU/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AvuakogAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAz5JREFUOE+tVTtu4zAQHQjppmWzwIJbEVCzpTpjbxD3grQHSOXKRXgCAT6EC7UBVAmp3KwBnmvfzNCyZTmxgeTZJsXx43B+HBHRE34ZkXgkerXFTheeiCkRrbB4UXmp4wSWz5raaQEMTM5TZwuiXoaKgV+6FsmkZQcSy0kA71yMTMGHanX+AzMMGLAQCxU1F/ZwjULPugazl82GM0NEKm/U8EqFwEkO3/EAT4grgl0nucwlk9pcpTTJ4VPA4g/Rb3yIRhhp507e9nTQmZ1OS5RO4sS7nIRPEeHXCHdkw9ZEW2yVE5oIS7peD58Avs7CN+PVCmHh21oOqBdjDzIs+FldPJ74TFESUSJEfVzy9U/dhu+AuOT6eBp6gGKyXEx8euO450ZE4CMfstMFT44broWw/itkYErWXRx+fFArt9Ca9os78TFed0LVIUsmIHrwbwaw3BEOnOk94qVpQ6Ka2HjxewJnfyd6jUtGDQLdWlzmYNYLeKbbGOucJsNabCq1Yub0o92rtR+i30V2dapxYVEePXcOjeCKPnYyit7BtKeNlZqHbr+gt7i+AChWA9RsRs03pxTQc67ouWpxyESvjK5Vs3DVSy3IpkxPm5X+wZoBi+MFHWW69/w8FRhc7VBe6HAhMB2b8Q0XqDzTNZtXUMnKMjwKVaCrB/CSUL7WSx/HsdJC86lFGXwnioTeOMPjV+szlFvrZLA5VMVK4y+41l4e1xfx7Z88o4hkilRUH/qKqwNVlgDgpvYCpH3XwAy5eMCRnezIUxffVXoDql2rTHFDO+pjWnTWzAfrYXn6BFECblUpWGrvPZvBipETjS5ydM7tdXpH41ZCEbBNy/+wFZu71QO2t9pgT+iZEf657Q1vpN94PQNDxUHeKR103LV9nPVOtDikcNKO+2naCw7yKBhOe9Hm79pe8C4/CfC2wDjXnqC94kEeBU3WwN7dt/2UScXas7zDl5GpkY+M8WKv2J7fd4Ib2rGTk+jsC2cleEM7jI9veF7B0MBJrsZqfKd/81q9pR2NZfwJK2JzsmIT1Ns8jUH0UusQBpU8d2JzsHiXg1zXGLqxfitUNTDT/nUUeqDBp2HZVr+Ocqi/Ty3Rf4Jn82xxfSNtAAAAAElFTkSuQmCC" + }, + "notes": "", + "owner": "Microsoft", + "privacyInformationUrl": "", + "publisher": "Microsoft" +} +"@ + +################################################## + +$Outlook = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Outlook", + "description": "Microsoft Outlook", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.outlook&hl=en", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Excel = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Excel", + "description": "Microsoft Excel", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.excel&hl=en", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Teams = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Teams", + "description": "Microsoft Teams", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.teams", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$OneDrive = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "OneDrive", + "description": "Microsoft OneDrive", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.skydrive", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$Word = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Word", + "description": "Microsoft Word", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.word", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + +################################################## + +$PowerPoint = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft PowerPoint", + "description": "Microsoft PowerPoint", + "publisher": "Microsoft Corporation", + "isFeatured": true, + largeIcon: { + "@odata.type": "#microsoft.graph.mimeContent", + "type": "$iconType", + "value": "$base64icon" + }, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.microsoft.office.powerpoint", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + + +$MicrosoftAuthenticator = @" +{ + "@odata.type": "#microsoft.graph.androidStoreApp", + "displayName": "Microsoft Authenticator-Android", + "description": "Microsoft Authenticator-Android", + "publisher": "Microsoft Corporation", + "isFeatured": true, + "appStoreUrl": "https://play.google.com/store/apps/details?id=com.azure.authenticator&hl=en_US", + "minimumSupportedOperatingSystem": { + "@odata.type": "#microsoft.graph.androidMinimumOperatingSystem", + "v4_0": true + } +} +"@ + + + +$iOS = @" +{ + "@odata.type": "#microsoft.graph.iosManagedAppProtection", + "displayName": "iOS_App_Protection Policy", + "description": "", + "periodOfflineBeforeAccessCheck": "PT12H", + "periodOnlineBeforeAccessCheck": "PT30M", + "allowedInboundDataTransferSources": "managedApps", + "allowedOutboundDataTransferDestinations": "managedApps", + "organizationalCredentialsRequired": false, + "allowedOutboundClipboardSharingLevel": "managedApps", + "dataBackupBlocked": true, + "deviceComplianceRequired": true, + "managedBrowserToOpenLinksRequired": false, + "saveAsBlocked": true, + "periodOfflineBeforeWipeIsEnforced": "P90D", + "pinRequired": true, + "maximumPinRetries": 5, + "simplePinBlocked": false, + "minimumPinLength": 4, + "pinCharacterSet": "numeric", + "periodBeforePinReset": "PT0S", + "allowedDataStorageLocations": [ + "oneDriveForBusiness" + ], + "contactSyncBlocked": false, + "printBlocked": true, + "fingerprintBlocked": false, + "disableAppPinIfDevicePinIsSet": false, + "appActionIfDeviceComplianceRequired": "block", + "appActionIfMaximumPinRetriesExceeded": "block", + "allowedOutboundClipboardSharingExceptionLength": 0, + "notificationRestriction": "allow", + "previousPinBlockCount": 0, + "managedBrowser": "notConfigured", + "maximumAllowedDeviceThreatLevel": "notConfigured", + "mobileThreatDefenseRemediationAction": "block", + "blockDataIngestionIntoOrganizationDocuments": false, + "allowedDataIngestionLocations": [ + "oneDriveForBusiness", + "sharePoint", + "camera" + ], + "targetedAppManagementLevels": "unmanaged", + "appDataEncryptionType": "whenDeviceLocked", + "deployedAppCount": 9, + "faceIdBlocked": false, + "appActionIfIosDeviceModelNotAllowed": "block", + "thirdPartyKeyboardsBlocked": false, + "filterOpenInToOnlyManagedApps": false, + "disableProtectionOfManagedOutboundOpenInData": false, + "protectInboundDataFromUnknownSources": false, + "exemptedAppProtocols": [ + { + "name": "Default", + "value": "tel;telprompt;skype;app-settings;calshow;itms;itmss;itms-apps;itms-appss;itms-services;" + } + ], + "apps": [ + { + "id": "com.microsoft.office.outlook.ios", + "version": "-518090279", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.outlook" + } + }, + { + "id": "com.microsoft.office.powerpoint.ios", + "version": "-740777841", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.powerpoint" + } + }, + { + "id": "com.microsoft.office.word.ios", + "version": "922692278", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.office.word" + } + }, + { + "id": "com.microsoft.onenote.ios", + "version": "107156768", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.onenote" + } + }, + { + "id": "com.microsoft.plannermobile.ios", + "version": "-175532278", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.plannermobile" + } + }, + { + "id": "com.microsoft.sharepoint.ios", + "version": "-585639021", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.sharepoint" + } + }, + { + "id": "com.microsoft.skydrive.ios", + "version": "-108719121", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.skydrive" + } + }, + { + "id": "com.microsoft.skype.teams.ios", + "version": "-1040529574", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.skype.teams" + } + }, + { + "id": "com.microsoft.stream.ios", + "version": "126860698", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.iosMobileAppIdentifier", + "bundleId": "com.microsoft.stream" + } + } + ] +} +"@ + +#################################################### + +$Android = @" +{ + "@odata.type": "#microsoft.graph.androidManagedAppProtection", + "displayName": "Android_Protection_Policy1", + "description": "", + "periodOfflineBeforeAccessCheck": "PT12H", + "periodOnlineBeforeAccessCheck": "PT30M", + "allowedInboundDataTransferSources": "managedApps", + "allowedOutboundDataTransferDestinations": "managedApps", + "organizationalCredentialsRequired": false, + "allowedOutboundClipboardSharingLevel": "managedApps", + "dataBackupBlocked": true, + "deviceComplianceRequired": true, + "managedBrowserToOpenLinksRequired": false, + "saveAsBlocked": true, + "periodOfflineBeforeWipeIsEnforced": "P90D", + "pinRequired": true, + "maximumPinRetries": 5, + "simplePinBlocked": false, + "minimumPinLength": 4, + "pinCharacterSet": "numeric", + "periodBeforePinReset": "PT0S", + "allowedDataStorageLocations": [], + "contactSyncBlocked": false, + "printBlocked": true, + "fingerprintBlocked": false, + "disableAppPinIfDevicePinIsSet": false, + "minimumRequiredOsVersion": null, + "minimumWarningOsVersion": null, + "minimumRequiredAppVersion": null, + "minimumWarningAppVersion": null, + "minimumWipeOsVersion": null, + "minimumWipeAppVersion": null, + "appActionIfDeviceComplianceRequired": "block", + "appActionIfMaximumPinRetriesExceeded": "block", + "pinRequiredInsteadOfBiometricTimeout": "PT30M", + "allowedOutboundClipboardSharingExceptionLength": 0, + "notificationRestriction": "allow", + "previousPinBlockCount": 0, + "managedBrowser": "notConfigured", + "maximumAllowedDeviceThreatLevel": "notConfigured", + "mobileThreatDefenseRemediationAction": "block", + "blockDataIngestionIntoOrganizationDocuments": false, + "allowedDataIngestionLocations": [ + "oneDriveForBusiness", + "sharePoint", + "camera" + ], + "appActionIfUnableToAuthenticateUser": null, + "targetedAppManagementLevels": "unmanaged", + "screenCaptureBlocked": true, + "disableAppEncryptionIfDeviceEncryptionIsEnabled": false, + "encryptAppData": true, + "deployedAppCount": 9, + "minimumRequiredPatchVersion": "0000-00-00", + "minimumWarningPatchVersion": "0000-00-00", + "minimumWipePatchVersion": "0000-00-00", + "allowedAndroidDeviceManufacturers": null, + "appActionIfAndroidDeviceManufacturerNotAllowed": "block", + "requiredAndroidSafetyNetDeviceAttestationType": "none", + "appActionIfAndroidSafetyNetDeviceAttestationFailed": "block", + "requiredAndroidSafetyNetAppsVerificationType": "none", + "appActionIfAndroidSafetyNetAppsVerificationFailed": "block", + "keyboardsRestricted": false, + "allowedAndroidDeviceModels": [], + "appActionIfAndroidDeviceModelNotAllowed": "block", + "exemptedAppPackages": [], + "approvedKeyboards": [], + "apps": [ + { + "id": "com.microsoft.office.excel.android", + "version": "-1789826587", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.excel" + } + }, + { + "id": "com.microsoft.office.onenote.android", + "version": "186482170", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.onenote" + } + }, + { + "id": "com.microsoft.office.outlook.android", + "version": "1146701235", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.outlook" + } + }, + { + "id": "com.microsoft.office.powerpoint.android", + "version": "1411665537", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.powerpoint" + } + }, + { + "id": "com.microsoft.office.word.android", + "version": "2122351424", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.office.word" + } + }, + { + "id": "com.microsoft.planner.android", + "version": "-1658524342", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.planner" + } + }, + { + "id": "com.microsoft.sharepoint.android", + "version": "84773357", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.sharepoint" + } + }, + { + "id": "com.microsoft.skydrive.android", + "version": "1887770705", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.skydrive" + } + }, + { + "id": "com.microsoft.teams.android", + "version": "1900143244", + "mobileAppIdentifier": { + "@odata.type": "#microsoft.graph.androidMobileAppIdentifier", + "packageId": "com.microsoft.teams" + } + } + ] +} +"@ + +################################################## + +$EnterpriseDomain = Get-EnterpriseDomain +$TenantName1 = Get-TenantName + +#################################################### + +$ManagedAppPolicy_WIP = @" +{ + "description": "", + "displayName": "Win10 WIP WE Policy - Assigned", + "enforcementLevel": "encryptAuditAndBlock", + "enterpriseDomain": "$EnterpriseDomain", + "enterpriseProtectedDomainNames": [], + "protectionUnderLockConfigRequired": false, + "dataRecoveryCertificate": null, + "revokeOnUnenrollDisabled": false, + "revokeOnMdmHandoffDisabled": false, + "rightsManagementServicesTemplateId": null, + "azureRightsManagementServicesAllowed": false, + "iconsVisible": true, + "protectedApps": [ + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft Teams", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "teams.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionDesktopApp", + "description": "", + "displayName": "Microsoft OneDrive", + "productName": "*", + "publisherName": "O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false, + "binaryName": "onedrive.exe", + "binaryVersionLow": "*", + "binaryVersionHigh": "*" + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Company Portal", + "productName": "Microsoft.CompanyPortal", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "OneNote", + "productName": "Microsoft.Office.OneNote", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "PowerPoint Mobile", + "productName": "Microsoft.Office.PowerPoint", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Excel Mobile", + "productName": "Microsoft.Office.Excel", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + }, + { + "@odata.type": "#microsoft.graph.windowsInformationProtectionStoreApp", + "description": "", + "displayName": "Word Mobile", + "productName": "Microsoft.Office.Word", + "publisherName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + "denied": false + } + ], + "exemptApps": [], + "protectedAppLockerFiles": [ + { + "displayName": "Office-365-ProPlus-1708-Allowed.xml", + "fileHash": "4107a857-0e0b-483f-8ff5-bb3ed095b434", + "file": "PEFwcExvY2tlclBvbGljeSBWZXJzaW9uPSIxIj4NCiAgPFJ1bGVDb2xsZWN0aW9uIFR5cGU9IkV4ZSIgRW5mb3JjZW1lbnRNb2RlPSJFbmFibGVkIj4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImQ2NjMyNzZjLTU0NGUtNGRlYS1iNzVjLTc1ZmMyZDRlMDI2NiIgTmFtZT0iTFlOQy5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQoJCSAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KCTwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIyMTk1NzFiNC1hMjU3LTRiNGMtYjg1Ni1lYmYwNjgxZWUwZjAiIE5hbWU9IkxZTkM5OS5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI1MzhkNDQzYS05ZmRiLTQ5MWUtOGJjMy1lNzhkYjljMzEwYzciIE5hbWU9Ik9ORU5PVEVNLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFTS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjZlZjA3YTkyLWYxZGItNDg4MC04YjQwLWRkMzliNzQzYTQ4NSIgTmFtZT0iV0lOV09SRC5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iN2Q3NGQxYWQtYTYwYi00ZWE4LWJiNjAtM2Q0MDQ4ODZkMTBiIiBOYW1lPSJPTkVOT1RFLkVYRSwgdmVyc2lvbiAxNi4wLjgyMDEuMjAyNSBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPTkVOT1RFLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSJPTkVOT1RFLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iODQwOWFhNjItMWI1Mi00ODRiLTg3ODctZDU3Y2M4NTI1ZTIxIiBOYW1lPSJPQ1BVQk1HUi5FWEUsIHZlcnNpb24gMTYuMC43ODcwLjIwMjAgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJBbGxvdyI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJPQ1BVQk1HUi5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImU2Y2NjY2QxLWE2MGYtNGNiNC04YjRiLTkwM2M3MDk3NmI1MCIgTmFtZT0iUE9XRVJQTlQuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iUE9XRVJQTlQuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJlYzc5ZWJiZS0zMTY5LTRhMjYtYWY1ZS1lMDc2YzkwOTY0OTUiIE5hbWU9Ik1TT1NZTkMuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iTVNPU1lOQy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYwYjM1MjZiLTE0ZTctNDQyOC05NzAzLTRkZmYyZWE0MDAwZCIgTmFtZT0iVUNNQVBJLkVYRSwgdmVyc2lvbiAxNi4wLjc4NzAuMjAyMCBhbmQgYWJvdmUsIGluIE1JQ1JPU09GVCBPRkZJQ0UgMjAxNiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IlVDTUFQSS5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC43ODcwLjIwMjAiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImYxYmVkY2IxLWNhMzQtNDdiNS04NmM4LTI1NjU0YjE3OGNiOSIgTmFtZT0iT1VUTE9PSy5FWEUsIHZlcnNpb24gMTYuMC44MjAxLjIwMjUgYW5kIGFib3ZlLCBpbiBNSUNST1NPRlQgT1VUTE9PSywgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkFsbG93Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImY5NjFlODQ2LTNmZTctNDY0Yy05NTA4LTAzNTMzN2QxZTg2OCIgTmFtZT0iRVhDRUwuRVhFLCB2ZXJzaW9uIDE2LjAuODIwMS4yMDI1IGFuZCBhYm92ZSwgaW4gTUlDUk9TT0ZUIE9GRklDRSAyMDE2LCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iQWxsb3ciPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iRVhDRUwuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9Db25kaXRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSJkNzAzMzE5Yy0wYzllLTQ0NjctYWQwOS03MTMzMDdlMzBkZjYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImRlOWYzNDYxLTY4NTYtNDA1ZC05NjI0LWE4MGNhNzAxZjZjYiIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDAzIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9ImFkZTFiODI4LTcwNTUtNDdmYy05OWJjLTQzMmNmN2QxMjA5ZSIgTmFtZT0iMjAwNyBNSUNST1NPRlQgT0ZGSUNFIFNZU1RFTSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9IjIwMDcgTUlDUk9TT0ZUIE9GRklDRSBTWVNURU0iIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYmIzODc3YjYtZjFhZC00YzgzLTk2ZTItZDZiZjc1ZTMzY2JmIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTAiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iMjM2MzVlZWYtYjFlMy00ZGEyLTg4NTktMDYzOGVjNjA3YjM4IiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTMiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iZDJkMDUyMGEtNTdkZS00YmQ1LWJjZDktYjNkNDcyMjFmODdhIiBOYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9IkVYQ0VMLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJMWU5DOTkuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuNzg3MC4yMDIwIiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik1TT1NZTkMuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIDIwMTYiIEJpbmFyeU5hbWU9Ik9DUFVCTUdSLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJQT1dFUlBOVC5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgMjAxNiIgQmluYXJ5TmFtZT0iVUNNQVBJLkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjc4NzAuMjAyMCIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9GRklDRSAyMDE2IiBCaW5hcnlOYW1lPSJXSU5XT1JELkVYRSI+DQogICAgICAgICAgPEJpbmFyeVZlcnNpb25SYW5nZSBMb3dTZWN0aW9uPSIxNi4wLjgyMDEuMjAyNSIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvRXhjZXB0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KICAgIDxGaWxlUHVibGlzaGVyUnVsZSBJZD0iYTMzNjVkZmYtNjA1Mi00MWE4LTgyYTYtMzQ0NDRlYzI1OTUzIiBOYW1lPSJNSUNST1NPRlQgT05FTk9URSwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPTkVOT1RFIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgICA8RXhjZXB0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9ORU5PVEUiIEJpbmFyeU5hbWU9Ik9ORU5PVEUuRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT05FTk9URSIgQmluYXJ5TmFtZT0iT05FTk9URU0uRVhFIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IjE2LjAuODIwMS4yMDI1IiBIaWdoU2VjdGlvbj0iKiIgLz4NCiAgICAgICAgPC9GaWxlUHVibGlzaGVyQ29uZGl0aW9uPg0KICAgICAgPC9FeGNlcHRpb25zPg0KICAgIDwvRmlsZVB1Ymxpc2hlclJ1bGU+DQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSIwYmE0YzE1MC0xY2IzLTRjYjktYWIwMi0yNjUyNzQ0MTk0MDkiIE5hbWU9Ik1JQ1JPU09GVCBPVVRMT09LLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIE9VVExPT0siIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICAgIDxFeGNlcHRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT1VUTE9PSyIgQmluYXJ5TmFtZT0iT1VUTE9PSy5FWEUiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iMTYuMC44MjAxLjIwMjUiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0V4Y2VwdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9IjY3MTRmODVmLWFkZGEtNGVjNy05NDdjLTc1NTJmN2Q5NDYyNSIgTmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSLCBmcm9tIE89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgRGVzY3JpcHRpb249IiIgVXNlck9yR3JvdXBTaWQ9IlMtMS0xLTAiIEFjdGlvbj0iRGVueSI+DQogICAgICA8Q29uZGl0aW9ucz4NCiAgICAgICAgPEZpbGVQdWJsaXNoZXJDb25kaXRpb24gUHVibGlzaGVyTmFtZT0iTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBQcm9kdWN0TmFtZT0iTUlDUk9TT0ZUIENMSVAgT1JHQU5JWkVSIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4JDQogICAgPEZpbGVQdWJsaXNoZXJSdWxlIElkPSI3NGFiMzY4Zi1hMTQ3LTRjZjktODBjMy01M2ZiNjY5YzQ4MzYiIE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSU5GT1BBVEgsIGZyb20gTz1NSUNST1NPRlQgQ09SUE9SQVRJT04sIEw9UkVETU9ORCwgUz1XQVNISU5HVE9OLCBDPVVTIiBEZXNjcmlwdGlvbj0iIiBVc2VyT3JHcm91cFNpZD0iUy0xLTEtMCIgQWN0aW9uPSJEZW55Ij4NCiAgICAgIDxDb25kaXRpb25zPg0KICAgICAgICA8RmlsZVB1Ymxpc2hlckNvbmRpdGlvbiBQdWJsaXNoZXJOYW1lPSJPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIFByb2R1Y3ROYW1lPSJNSUNST1NPRlQgT0ZGSUNFIElORk9QQVRIIiBCaW5hcnlOYW1lPSIqIj4NCiAgICAgICAgICA8QmluYXJ5VmVyc2lvblJhbmdlIExvd1NlY3Rpb249IioiIEhpZ2hTZWN0aW9uPSIqIiAvPg0KICAgICAgICA8L0ZpbGVQdWJsaXNoZXJDb25kaXRpb24+DQogICAgICA8L0NvbmRpdGlvbnM+DQogICAgPC9GaWxlUHVibGlzaGVyUnVsZT4NCiAgICA8RmlsZVB1Ymxpc2hlclJ1bGUgSWQ9Ijk3NjlkMjA5LTg1ZjgtNDM4OS1iZGM2LWRkY2EzMGYxYzY3MSIgTmFtZT0iTUlDUk9TT0ZUIE9GRklDRSBIRUxQIFZJRVdFUiwgZnJvbSBPPU1JQ1JPU09GVCBDT1JQT1JBVElPTiwgTD1SRURNT05ELCBTPVdBU0hJTkdUT04sIEM9VVMiIERlc2NyaXB0aW9uPSIiIFVzZXJPckdyb3VwU2lkPSJTLTEtMS0wIiBBY3Rpb249IkRlbnkiPg0KICAgICAgPENvbmRpdGlvbnM+DQogICAgICAgIDxGaWxlUHVibGlzaGVyQ29uZGl0aW9uIFB1Ymxpc2hlck5hbWU9Ik89TUlDUk9TT0ZUIENPUlBPUkFUSU9OLCBMPVJFRE1PTkQsIFM9V0FTSElOR1RPTiwgQz1VUyIgUHJvZHVjdE5hbWU9Ik1JQ1JPU09GVCBPRkZJQ0UgSEVMUCBWSUVXRVIiIEJpbmFyeU5hbWU9IioiPg0KICAgICAgICAgIDxCaW5hcnlWZXJzaW9uUmFuZ2UgTG93U2VjdGlvbj0iKiIgSGlnaFNlY3Rpb249IioiIC8+DQogICAgICAgIDwvRmlsZVB1Ymxpc2hlckNvbmRpdGlvbj4NCiAgICAgIDwvQ29uZGl0aW9ucz4NCiAgICA8L0ZpbGVQdWJsaXNoZXJSdWxlPg0KCTwvUnVsZUNvbGxlY3Rpb24+DQo8L0FwcExvY2tlclBvbGljeT4=", + "id": "0c5c4541-87a8-478b-b9fc-4206d0f421f9", + "@odata.type": "#microsoft.graph.windowsInformationProtectionAppLockerFile" + } + ], + "exemptAppLockerFiles": [], + "enterpriseIPRanges": [], + "enterpriseIPRangesAreAuthoritative": false, + "enterpriseProxyServers": [], + "enterpriseInternalProxyServers": [], + "enterpriseProxyServersAreAuthoritative": false, + "neutralDomainResources": [], + "windowsHelloForBusinessBlocked": false, + "pinMinimumLength": 4, + "pinUppercaseLetters": "notAllow", + "pinLowercaseLetters": "notAllow", + "pinSpecialCharacters": "notAllow", + "pinExpirationDays": 0, + "numberOfPastPinsRemembered": 0, + "passwordMaximumAttemptCount": 0, + "minutesOfInactivityBeforeDeviceLock": 0, + "mdmEnrollmentUrl": "https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc", + "@odata.type": "#microsoft.graph.windowsInformationProtectionPolicy" +} +"@ + +#################################################### +$ManagedAppPolicy_Windows = @" +{ + "displayName":"Windows Browser App Protection Worked", + "description":"", + "roleScopeTagIds":["0"], + "version":"\"9c005f0e-0000-0100-0000-65fdb1ab0000\"", + "isAssigned":false, + "deployedAppCount":1, + "printBlocked":true, + "allowedInboundDataTransferSources":"allApps", + "allowedOutboundClipboardSharingLevel":"none", + "allowedOutboundDataTransferDestinations":"none", + "appActionIfUnableToAuthenticateUser":null, + "maximumAllowedDeviceThreatLevel":"notConfigured", + "mobileThreatDefenseRemediationAction":"block", + "minimumRequiredSdkVersion":null, + "minimumWipeSdkVersion":null, + "minimumRequiredOsVersion":null, + "minimumWarningOsVersion":null, + "minimumWipeOsVersion":null, + "minimumRequiredAppVersion":null, + "minimumWarningAppVersion":null, + "minimumWipeAppVersion":null, + "maximumRequiredOsVersion":null, + "maximumWarningOsVersion":null, + "maximumWipeOsVersion":null, + "periodOfflineBeforeWipeIsEnforced":"P90D", + "periodOfflineBeforeAccessCheck":"P1D" +} + +"@ + +#################################################### + +$BaselineSecurity_Windows = @" +{ + "displayName":"Windows Security Baseline", + "description":"Windows Security Baseline", + "isAssigned":false, + "isMigratingToConfigurationPolicy":null, + "templateId":"034ccd46-190c-4afc-adf1-ad7cc11262eb", + "roleScopeTagIds":["0"] +} +"@ +#> +#################################################### + +$windows_endpoint = @" + +{ + "@odata.type": "#microsoft.graph.windows10EndpointProtectionConfiguration", + "description": "Silently configure BitLocker Encryption", + "displayName": "Windows_Endpoint_Profile", + "version": 1, + "firewallBlockStatefulFTP": false, + "firewallIdleTimeoutForSecurityAssociationInSeconds": null, + "firewallPreSharedKeyEncodingMethod": "deviceDefault", + "firewallIPSecExemptionsAllowNeighborDiscovery": false, + "firewallIPSecExemptionsAllowICMP": false, + "firewallIPSecExemptionsAllowRouterDiscovery": false, + "firewallIPSecExemptionsAllowDHCP": false, + "firewallCertificateRevocationListCheckMethod": "deviceDefault", + "firewallMergeKeyingModuleSettings": false, + "firewallPacketQueueingMethod": "deviceDefault", + "firewallProfileDomain": null, + "firewallProfilePublic": null, + "firewallProfilePrivate": null, + "defenderAttackSurfaceReductionExcludedPaths": [], + "defenderGuardedFoldersAllowedAppPaths": [], + "defenderAdditionalGuardedFolders": [], + "defenderExploitProtectionXml": null, + "defenderExploitProtectionXmlFileName": null, + "defenderSecurityCenterBlockExploitProtectionOverride": false, + "appLockerApplicationControl": "notConfigured", + "smartScreenEnableInShell": false, + "smartScreenBlockOverrideForFiles": false, + "applicationGuardEnabled": false, + "applicationGuardBlockFileTransfer": "notConfigured", + "applicationGuardBlockNonEnterpriseContent": false, + "applicationGuardAllowPersistence": false, + "applicationGuardForceAuditing": false, + "applicationGuardBlockClipboardSharing": "notConfigured", + "applicationGuardAllowPrintToPDF": false, + "applicationGuardAllowPrintToXPS": false, + "applicationGuardAllowPrintToLocalPrinters": false, + "applicationGuardAllowPrintToNetworkPrinters": false, + "bitLockerDisableWarningForOtherDiskEncryption": true, + "bitLockerEnableStorageCardEncryptionOnMobile": false, + "bitLockerEncryptDevice": true, + "bitLockerRemovableDrivePolicy": { + "encryptionMethod": null, + "requireEncryptionForWriteAccess": false, + "blockCrossOrganizationWriteAccess": false + } + } +"@ + + + + +#################################################### + +# Setting AAD Group + +$AADGroup = Read-Host -Prompt "Enter the Azure AD Group name where policies will be assigned" + +write-host "Checking group membership" -ForegroundColor cyan +$group = Validate-AADGroup -GroupName $AADGroup + +<# $TargetGroupId = (get-AADGroup -GroupName "$AADGroup").id + + if($TargetGroupId -eq $null -or $TargetGroupId -eq ""){ + + Write-Host "AAD Group - '$AADGroup' doesn't exist, please specify a valid AAD Group..." -ForegroundColor Red + Write-Host + + + $AADGroup = Read-Host -Prompt "Enter the Azure AD Group name where policies will be assigned" + $TargetGroupId = (get-AADGroup -GroupName "$AADGroup").id + + } +#> + +Write-Host + +############################################################################## + +Write-Host +Write-Host "Adding Terms and Conditions from JSON..." -ForegroundColor Cyan +Write-Host "Creating Terms and Conditions via Graph" +$CreateResult = Add-TermsAndConditions -JSON $JSON +write-host "Terms and Conditions created with id" $CreateResult.id + +Write-Host +Write-Host "Starting Assign-TermsAndConditions Function" -ForegroundColor cyan + if ($CreateResult.id) { + $Assign_Policy = New-MgBetaDeviceManagementTermAndConditionGroupAssignment -TermsAndConditionsId $CreateResult.id -TargetGroupId $group.id + Write-Host "Assigned '$($group.displayName)' with Group ID of '$($group.id)' to Customer Terms and Conditions/$($CreateResult.id)" + } else { + Write-Host "No Terms and Conditions ID was returned, unable to assign." + } + + +#################################################### + +Write-Host "Adding macOS Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_macOS = Add-DeviceCompliancePolicy -JSON $JSON_macOS + +Write-Host "Compliance Policy created as" $CreateResult_macOS.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_macOS = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_macOS.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_macOS.displayName)/$($CreateResult_macOS.id)" +Write-Host + +#################################################### + +Write-Host "Adding Android Work Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_AndroidWork = Add-DeviceCompliancePolicy -JSON $JSON_AndroidWork + +Write-Host "Compliance Policy created as" $CreateResult_AndroidWork.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_AndroidWork.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_AndroidWork.displayName)/$($CreateResult_AndroidWork.id)" +Write-Host + +#################################################### + +Write-Host "Adding Android Device Owner Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_AndroidOwner = Add-DeviceCompliancePolicy -JSON $JSON_AndroidOwner + +Write-Host "Compliance Policy created as" $CreateResult_AndroidOwner.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_AndroidOwner.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_AndroidOwner.displayName)/$($CreateResult_AndroidOwner.id)" +Write-Host + +#################################################### + +Write-Host "Adding iOS Compliance Policy from JSON..." -ForegroundColor Yellow +Write-Host + +$CreateResult_iOS = Add-DeviceCompliancePolicy -JSON $JSON_iOS + +Write-Host "Compliance Policy created as" $CreateResult_iOS.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_iOS = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_iOS.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_iOS.displayName)/$($CreateResult_iOS.id)" +Write-Host + +##################################################### + +Write-Host "Adding Windows 10 Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_Windows = Add-DeviceCompliancePolicy -JSON $JSON_Windows10 + +Write-Host "Compliance Policy created as" $CreateResult_Windows.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_Windows.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Windows.displayName)/$($CreateResult_Windows.id)" +Write-Host +##################################################### + +Write-Host "Adding Windows 11 Compliance Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_Windows = Add-DeviceCompliancePolicy -JSON $JSON_Windows11 + +Write-Host "Compliance Policy created as" $CreateResult_Windows.id +write-host +write-host "Assigning Compliance Policy to AAD Group '$AADGroup'" -f Cyan + +$Assign_Android = Add-DeviceCompliancePolicyAssignment -CompliancePolicyId $CreateResult_Windows.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Windows.displayName)/$($CreateResult_Windows.id)" +Write-Host +#################################################### + +Write-Host "Adding Windows 10 Endpoint Protection Profile from JSON..." -ForegroundColor Yellow + +$CreateResult_Endpoint = Add-DeviceConfigurationPolicy -JSON $windows_endpoint + +Write-Host "Device Restriction Policy created as" $CreateResult_Endpoint.id +write-host +write-host "Assigning Windows 10 Endpoint Protection Profile to AAD Group '$AADGroup'" -f Cyan + +$Assign_Endpoint = Add-DeviceConfigurationPolicyAssignment -ConfigurationPolicyId $CreateResult_Endpoint.id -TargetGroupId $TargetGroupId + +Write-Host "Assigned '$AADGroup' to $($CreateResult_Endpoint.displayName)/$($CreateResult_Endpoint.id)" +Write-Host + + +#################################################### + +Write-Host "Adding Windows 10 Update Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_UpdateProfile = Add-WindowsUpdatePolicy -JSON $JSON_10QualityUpdate + +Write-Host "Windows 10 Update Policy created as" $CreateResult_UpdateProfile.id +write-host +Write-Host "Adding Windows 11 Update Policy from JSON..." -ForegroundColor Yellow + +$CreateResult_UpdateProfile = Add-WindowsUpdatePolicy -JSON $JSON_11QualityUpdate + +Write-Host "Windows 11 Update Policy created as" $CreateResult_UpdateProfile.id +write-host + + +################################################## + + +################################################################################## + +write-host "Publishing" ($JSON1 | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Application = Add-MDMApplication -JSON $JSON1 + +Write-Host "Application created as $($Create_Application.displayName)/$($create_Application.id)" + +$ApplicationId = $Create_Application.id + +$Assign_Application = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" +Write-Host "Assigned '$AADGroup' to $($Create_Application.displayName)/$($Create_Application.id) with" $Assign_Application.InstallIntent "install Intent" + +Write-Host + +# Set parameter culture for script execution +$culture = "EN-US" + +# Backup current culture +$OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture +$OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture + + +# Set new Culture for script execution +[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture +[System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture + + +################################################## + +write-host "Publishing" ($mac_OfficeApps | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_macApps = Add-MDMApplication -JSON $mac_OfficeApps + +Write-Host "Application created as $($Create_macApps.displayName)/$($create_macApps.id)" + +$ApplicationId = $Create_macApps.id + +$Assign_Application = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" +Write-Host "Assigned '$AADGroup' to $($Create_Application.displayName)/$($Create_Application.id) with" $Assign_Application.InstallIntent "install Intent" + +Write-Host + +#################################################### + +$itunesApps = Get-itunesApplication -SearchString "Microsoft" -Limit 50 + +#region Office Example +$Applications = 'Microsoft Outlook','Microsoft Excel','OneDrive','Microsoft Authenticator', 'Microsoft Teams', 'Microsoft Word', 'Microsoft PowerPoint' +#endregion + +# If application list is specified +if($Applications) { + + # Looping through applications list + foreach($Application in $Applications){ + + $itunesApp = $itunesApps.results | ? { ($_.trackName).contains("$Application") } + + # if single application count is greater than 1 loop through names + if($itunesApp.count -gt 1){ + + $itunesApp.count + write-host "More than 1 application was found in the itunes store" -f Cyan + + foreach($iapp in $itunesApp){ + + $Create_App = Add-iOSApplication -itunesApp $iApp + + $ApplicationId = $Create_App.id + + Write-Host "Creating Application" + Write-Host + Write-Host + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + } + + # Single application found, adding application + elseif($itunesApp){ + + $Create_App = Add-iOSApplication -itunesApp $itunesApp + + $ApplicationId = $Create_App.id + + Write-Host "Creating Application" + Write-Host + Write-Host + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + # if application isn't found in itunes returning doesn't exist + else { + + write-host + write-host "Application '$Application' doesn't exist" -f Red + write-host + + } + + } + +} + +# No Applications have been specified +else { + + # if there are results returned from itunes query + if($itunesApps.results){ + + write-host + write-host "Number of iOS applications to add:" $itunesApps.results.count -f Yellow + Write-Host + + # Looping through applications returned from itunes + foreach($itunesApp in $itunesApps.results){ + + $Create_App = Add-iOSApplication -itunesApp $itunesApp + + $ApplicationId = $Create_App.id + + $Assign_App = Add-ApplicationAssignment -ApplicationId $ApplicationId -TargetGroupId $TargetGroupId -InstallIntent "required" + Write-Host "Assigned '$AADGroup' to $($Create_App.displayName)/$($create_App.id) with" $Assign_App.InstallIntent "install Intent" + + Write-Host + + } + + } + + # No applications returned from itunes + else { + + write-host + write-host "No applications found..." -f Red + write-host + + } + +} + + +################################################## + + + + + + + +Write-Host "Adding App Protection Policies to Intune..." -ForegroundColor Cyan +Write-Host + +Write-Host "Adding iOS Managed App Policy from JSON..." -ForegroundColor Yellow +Write-Host "Creating Policy via Graph" + +$CreateResult = Add-ManagedAppPolicy -Json $iOS +write-host "Policy created with id" $CreateResult.id + +$MAM_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-ManagedAppPolicy -Id $MAM_PolicyID -TargetGroupId $TargetGroupId -OS iOS +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + +write-host "Adding Android Managed App Policy from JSON..." -f Yellow +Write-Host "Creating Policy via Graph" + +$CreateResult = Add-ManagedAppPolicy -Json $Android +write-host "Policy created with id" $CreateResult.id + +$MAM_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-ManagedAppPolicy -Id $MAM_PolicyID -TargetGroupId $TargetGroupId -OS Android +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + +#################################################### + +<# +Write-Host "Adding Windows Baseline Security Policy from JSON..." -ForegroundColor Yellow + +$CreateResult = Add-WindowsBaselineSecurity -JSON $BaselineSecurity_Windows + +write-host "Policy created with id" $CreateResult.id + +$WinBaselineSec_PolicyID = $CreateResult.id + +# $Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host +#> + +#################################################### + +Write-Host "Adding Windows Edge Information Protection Without Enrollment Policy from JSON..." -ForegroundColor Yellow + +$CreateResult = Add-WindowsInformationProtectionPolicy -JSON $ManagedAppPolicy_Windows + +write-host "Policy created with id" $CreateResult.id + +$WIP_PolicyID = $CreateResult.id + +$Assign_Policy = Assign-WindowsInformationProtectionPolicy -Id $WIP_PolicyID -TargetGroupId $TargetGroupId +Write-Host "Assigned '$AADGroup' to $($CreateResult.displayName)/$($CreateResult.id)" + +Write-Host + + +############################################################## + +write-host "Publishing Android" ($MicrosoftAuthenticator | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_MicrosoftAuthenticator = Add-AndroidApplication -JSON $MicrosoftAuthenticator + +Write-Host "Application created as $($Create_MicrosoftAuthenticator.displayName)/$($create_MicrosoftAuthenticator.id)" +Write-Host + + + $ApplicationId1 = $Create_MicrosoftAuthenticator.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + + +################################################## + +write-host "Publishing Android" ($Outlook | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Outlook = Add-AndroidApplication -JSON $Outlook + +Write-Host "Application created as $($Create_Outlook.displayName)/$($create_Outlook.id)" +Write-Host + + + $ApplicationId1 = $Create_Outlook.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_Outlook.displayName)/$($create_Outlook.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + +################################################## + +write-host "Publishing Android" ($Word| ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Word = Add-AndroidApplication -JSON $Word + +Write-Host "Application created as $($Create_Word.displayName)/$($create_Word.id)" +Write-Host + + $ApplicationId1 = $Create_Word.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($Excel | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Excel = Add-AndroidApplication -JSON $Excel +Write-Host "Application created as $($Create_Excel.displayName)/$($create_Excel.id)" +Write-Host + + $ApplicationId1 = $Create_Excel.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + + +################################################## + +write-host "Publishing Android" ($Teams | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_Teams = Add-AndroidApplication -JSON $Teams + +Write-Host "Application created as $($Create_Teams.displayName)/$($create_Teams.id)" +Write-Host + + $ApplicationId1 = $Create_Teams.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($OneDrive | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_OneDrive = Add-AndroidApplication -JSON $OneDrive + +Write-Host "Application created as $($Create_OneDrive.displayName)/$($create_OneDrive.id)" +Write-Host + + $ApplicationId1 = $Create_OneDrive.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + Write-Host + +################################################## + +write-host "Publishing Android" ($PowerPoint | ConvertFrom-Json).displayName -ForegroundColor Yellow + +$Create_PowerPoint = Add-AndroidApplication -JSON $PowerPoint + +Write-Host "Application created as $($Create_PowerPoint.displayName)/$($create_PowerPoint.id)" +Write-Host + + $ApplicationId1 = $Create_PowerPoint.id + + $Assign_App1 = Add-ApplicationAssignment -ApplicationId $ApplicationId1 -TargetGroupId $TargetGroupId -InstallIntent "Required" + Write-Host "Assigned '$AADGroup' to $($Create_App1.displayName)/$($create_App1.id) with" $Assign_App1.InstallIntent "install Intent" + + Write-Host + Write-Host + Write-Host + +$block = @" + + __ __ .______ _______ ___ .___________. _______ _______ .______ ____ ____ __ __ ___ __ +| | | | | _ \ | \ / \ | || ____|| \ | _ \ \ \ / / | | | | / \ | | +| | | | | |_) | | .--. | / ^ \ `---| |----`| |__ | .--. | | |_) | \ \/ / | |__| | / ^ \ | | ______ +| | | | | ___/ | | | | / /_\ \ | | | __| | | | | | _ < \_ _/ | __ | / /_\ \ | | |______| +| `--' | | | | '--' | / _____ \ | | | |____ | '--' | | |_) | | | | | | | / _____ \ | `----. + \______/ | _| |_______/ /__/ \__\ |__| |_______||_______/ |______/ |__| |__| |__| /__/ \__\ |_______| + +.___________. __ __ ___ .__ __. __ ___ _______. .___________..___ ___. __ .__ __. __ __ _______. __ +| || | | | / \ | \ | | | |/ / / | | || \/ | | | | \ | | | | | | / || | +`---| |----`| |__| | / ^ \ | \| | | ' / | (----` `---| |----`| \ / | | | | \| | | | | | | (----`| | + | | | __ | / /_\ \ | . ` | | < \ \ | | | |\/| | | | | . ` | | | | | \ \ | | + | | | | | | / _____ \ | |\ | | . \ .----) | __ | | | | | | | | | |\ | | `--' | .----) | |__| + |__| |__| |__| /__/ \__\ |__| \__| |__|\__\ |_______/ (_ ) |__| |__| |__| |__| |__| \__| \______/ |_______/ (__) + |/ + +"@ + +Write-Host $block -ForegroundColor Cyan + + + + From 916fc270e1ada261da10525497704f5cf1a006a1 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:04:20 -0400 Subject: [PATCH 031/107] Add files via upload Remediation script pair to check and remediate a warning message before the device locks due to timeout settings. --- Detect-TimeoutWarning.ps1 | 11 +++++++++++ Remediate-TimeoutWarning.ps1 | 12 ++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 Detect-TimeoutWarning.ps1 create mode 100644 Remediate-TimeoutWarning.ps1 diff --git a/Detect-TimeoutWarning.ps1 b/Detect-TimeoutWarning.ps1 new file mode 100644 index 0000000..9bbd56a --- /dev/null +++ b/Detect-TimeoutWarning.ps1 @@ -0,0 +1,11 @@ +$idletime = 8 * 60 # idle time before the lock in seconds (e.g., 8 minutes for a 10-minute lock) +$lastinputinfo = new-object "lastinputinfo" +$lastinputinfo.cbsize = [system.runtime.interopservices.marshal]::sizeof($lastinputinfo) +[system.runtime.interopservices.marshal]::getlastwin32error() +if ([system.runtime.interopservices.marshal]::getlastinputinfo([ref]$lastinputinfo)) { + $idletimepassed = ((get-tickcount) - $lastinputinfo.dwtime) / 1000 + if ($idletimepassed -gt $idletime) { + exit 1 # Issue detected + } +} +exit 0 # No issue detected diff --git a/Remediate-TimeoutWarning.ps1 b/Remediate-TimeoutWarning.ps1 new file mode 100644 index 0000000..4758b59 --- /dev/null +++ b/Remediate-TimeoutWarning.ps1 @@ -0,0 +1,12 @@ +$idletime = 8 * 60 # idle time before the lock in seconds (e.g., 8 minutes for a 10-minute lock) +$warningtime = 2 * 60 # warning time in seconds (2 minutes before lock) + +$lastinputinfo = new-object "lastinputinfo" +$lastinputinfo.cbsize = [system.runtime.interopservices.marshal]::sizeof($lastinputinfo) +[system.runtime.interopservices.marshal]::getlastwin32error() +if ([system.runtime.interopservices.marshal]::getlastinputinfo([ref]$lastinputinfo)) { + $idletimepassed = ((get-tickcount) - $lastinputinfo.dwtime) / 1000 + if ($idletimepassed -gt $idletime - $warningtime -and $idletimepassed -lt $idletime) { + [system.windows.forms.messagebox]::show("Your device will lock in 2 minutes due to inactivity.", "Inactivity Warning") + } +} From 6ad30bd13532ea737ba711a88d2135f4cc06a62e Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:14:33 -0400 Subject: [PATCH 032/107] Add files via upload Script to be deployed via GPO to initiate BitLocker encryption. --- Initiate-BitLocker.ps1 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Initiate-BitLocker.ps1 diff --git a/Initiate-BitLocker.ps1 b/Initiate-BitLocker.ps1 new file mode 100644 index 0000000..fbf1ff7 --- /dev/null +++ b/Initiate-BitLocker.ps1 @@ -0,0 +1,5 @@ +$BLV = Get-BitLockerVolume -MountPoint "C:" +if ($BLV.VolumeStatus -eq "FullyDecrypted") { + Add-BitLockerKeyProtector -MountPoint "C:" -RecoveryPasswordProtector + Enable-BitLocker -MountPoint "C:" -TpmProtector +} From 0660d96c3b0f7e2ba4682c386515eee0a9e27cd8 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:24:56 -0400 Subject: [PATCH 033/107] Add files via upload Intune remediation script pair to check for disk space less than 15% and to run basic cleanup for devices that trigger this detection. --- Detect-DiskSpace.ps1 | 13 +++++++++++++ Remediate-DiskSpace.ps1 | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Detect-DiskSpace.ps1 create mode 100644 Remediate-DiskSpace.ps1 diff --git a/Detect-DiskSpace.ps1 b/Detect-DiskSpace.ps1 new file mode 100644 index 0000000..834069d --- /dev/null +++ b/Detect-DiskSpace.ps1 @@ -0,0 +1,13 @@ +$threshold = 15 +$drives = Get-PSDrive -PSProvider FileSystem + +foreach ($drive in $drives) { + $freeSpacePercent = ($drive.Free / $drive.Used) * 100 + if ($freeSpacePercent -lt $threshold) { + Write-Output "Less than 15% free space on drive $($drive.Name)" + exit 1 + } +} + +Write-Output "More than 15% free space on all drives" +exit 0 diff --git a/Remediate-DiskSpace.ps1 b/Remediate-DiskSpace.ps1 new file mode 100644 index 0000000..aae958e --- /dev/null +++ b/Remediate-DiskSpace.ps1 @@ -0,0 +1,20 @@ +$threshold = 15 +$drives = Get-PSDrive -PSProvider FileSystem + +foreach ($drive in $drives) { + $freeSpacePercent = ($drive.Free / $drive.Used) * 100 + if ($freeSpacePercent -lt $threshold) { + # Example cleanup actions + Write-Output "Cleaning up drive $($drive.Name)" + + # Delete temporary files + Remove-Item -Path "$($env:TEMP)\*" -Recurse -Force -ErrorAction SilentlyContinue + + # Empty Recycle Bin + Clear-RecycleBin -Force -ErrorAction SilentlyContinue + + # Add more cleanup actions as needed + } +} + +Write-Output "Remediation completed" From 19317436cdf59fa86e6251db11c8570b25f9b9f9 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:42:45 -0400 Subject: [PATCH 034/107] Update Detect-WingetUpdates.ps1 --- Detect-WingetUpdates.ps1 | 44 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/Detect-WingetUpdates.ps1 b/Detect-WingetUpdates.ps1 index dd89304..b16c97b 100644 --- a/Detect-WingetUpdates.ps1 +++ b/Detect-WingetUpdates.ps1 @@ -1,34 +1,14 @@ -# Paths to check for the winget executable -$possibleWingetPaths = @( - "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe", - "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe", - "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" -) - -# Function to check if winget is installed -function Get-WingetPath { - foreach ($path in $possibleWingetPaths) { - $resolvedPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue - if ($resolvedPaths) { - return $resolvedPaths.FullName - } - } - return $null -} - -$wingetPath = Get-WingetPath - -if ($null -eq $wingetPath) { - Write-Error "Winget is not installed or the path is incorrect." - exit 1 -} - -# Check if there are updates available -$updates = & $wingetPath list --upgrade -if ($updates) { - Write-Output "Updates available" - exit 1 +# Get the list of applications with upgrades available +$upgradeList = winget list --upgrade-available + +# Check if any upgrades are available +if ($upgradeList) { + # If any applications have updates available, list them + Write-Host "Applications with updates available:" + Write-Host $upgradeList + exit 1 # Exit with 1 indicating updates are available (non-compliance) } else { - Write-Output "No updates available" - exit 0 + # No upgrades available, all apps are up-to-date + Write-Host "All applications are up-to-date" + exit 0 # Exit with 0 indicating compliance } From b16183fa06327634fa2920be0308419787787f21 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:43:17 -0400 Subject: [PATCH 035/107] Update Remediate-WingetUpdates.ps1 --- Remediate-WingetUpdates.ps1 | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/Remediate-WingetUpdates.ps1 b/Remediate-WingetUpdates.ps1 index 6679d54..fac146e 100644 --- a/Remediate-WingetUpdates.ps1 +++ b/Remediate-WingetUpdates.ps1 @@ -1,28 +1,9 @@ -# Paths to check for the winget executable -$possibleWingetPaths = @( - "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe", - "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe", - "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" -) - -# Function to check if winget is installed -function Get-WingetPath { - foreach ($path in $possibleWingetPaths) { - $resolvedPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue - if ($resolvedPaths) { - return $resolvedPaths.FullName - } - } - return $null +# Attempt to update all applications with updates available +$upgradeResult = winget upgrade --all --silent + +# Log the result of the upgrade attempt +if ($upgradeResult) { + Write-Host "Applications updated successfully or updates in progress." +} else { + Write-Host "No applications needed updates." } - -$wingetPath = Get-WingetPath - -if ($null -eq $wingetPath) { - Write-Error "Winget is not installed or the path is incorrect." - exit 1 -} - -# Upgrade all winget-installed software -& $wingetPath upgrade --all --silent -Write-Output "Updates installed" From 5a82a58b8b399a04e791cfe7649ca601c6a82593 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:47:02 -0400 Subject: [PATCH 036/107] Add files via upload Detection and Remediation scripts for specific list of applications managed using WinGet --- Detect-WinGetUpdates.ps1 | 32 ++++++++++++++++++++++++++++++++ Remediate-WinGetUpdates.ps1 | 6 ++++++ 2 files changed, 38 insertions(+) create mode 100644 Detect-WinGetUpdates.ps1 create mode 100644 Remediate-WinGetUpdates.ps1 diff --git a/Detect-WinGetUpdates.ps1 b/Detect-WinGetUpdates.ps1 new file mode 100644 index 0000000..dbf32cb --- /dev/null +++ b/Detect-WinGetUpdates.ps1 @@ -0,0 +1,32 @@ +# List of application IDs to check for updates +$apps = @( + "Google.Chrome", + "Mozilla.Firefox", + "VideoLAN.VLC", + "Adobe.Acrobat.Reader.64-bit" +) + +# Initialize an array to store outdated apps +$outdatedApps = @() + +# Get the list of applications with upgrades available +$upgradeList = winget list --upgrade-available + +# Loop through each app and check if it's listed for upgrade +foreach ($app in $apps) { + if ($upgradeList -match $app) { + Write-Host "$app needs an update" + $outdatedApps += $app + } else { + Write-Host "$app is up-to-date" + } +} + +# Output result for Intune detection +if ($outdatedApps.Count -gt 0) { + Write-Host "Outdated applications detected: $($outdatedApps -join ', ')" + exit 1 # Exit with 1 if outdated apps are found (indicating non-compliance) +} else { + Write-Host "All applications are up-to-date" + exit 0 # Exit with 0 if all apps are up-to-date (indicating compliance) +} diff --git a/Remediate-WinGetUpdates.ps1 b/Remediate-WinGetUpdates.ps1 new file mode 100644 index 0000000..db1d4aa --- /dev/null +++ b/Remediate-WinGetUpdates.ps1 @@ -0,0 +1,6 @@ +# Upgrade_Apps.ps1 +$appsToUpgrade = @("Apple.Bonjour", "Apple.iTunes", "VideoLAN.VLC", "Adobe.Acrobat.Reader.64-bit") + +foreach ($app in $appsToUpgrade) { + winget upgrade --id $app --exact --silent --accept-source-agreements --accept-package-agreements --disable-interactivity +} From f5b99640bba305b13020415d7a4d1c39dbef08da Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:05:22 -0500 Subject: [PATCH 037/107] Add files via upload Check for and remove SCCM client agent when moving away from SCCM co-management to fully managed Intune device. --- Detect-SCCMClientAgent.ps1 | 12 ++++++++++++ Remediate-SCCMClientAgent.ps1 | 14 ++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Detect-SCCMClientAgent.ps1 create mode 100644 Remediate-SCCMClientAgent.ps1 diff --git a/Detect-SCCMClientAgent.ps1 b/Detect-SCCMClientAgent.ps1 new file mode 100644 index 0000000..cee497a --- /dev/null +++ b/Detect-SCCMClientAgent.ps1 @@ -0,0 +1,12 @@ +# Detection script for Intune Proactive Remediation + +# Path to the SCCM client agent executable +$SCCMClientExecutable = "C:\Windows\CCM\CcmExec.exe" + +if (Test-Path -Path $SCCMClientExecutable) { + Write-Output "SCCM client agent executable detected." + exit 1 +} else { + Write-Output "SCCM client agent executable not detected." + exit 0 +} diff --git a/Remediate-SCCMClientAgent.ps1 b/Remediate-SCCMClientAgent.ps1 new file mode 100644 index 0000000..c9a5f0a --- /dev/null +++ b/Remediate-SCCMClientAgent.ps1 @@ -0,0 +1,14 @@ +# Remediation script for Intune Proactive Remediation + +# Path to the SCCM client agent executable and uninstall program +$SCCMClientExecutable = "C:\Windows\CCM\CcmExec.exe" +$UninstallCommand = "C:\Windows\CCMSetup\CCMSetup.exe /uninstall" + +# Check if the SCCM client agent executable is present +if (Test-Path -Path $SCCMClientExecutable) { + Write-Output "SCCM client agent executable detected. Initiating uninstall process." + Start-Process -FilePath "cmd.exe" -ArgumentList "/c", $UninstallCommand -Wait -NoNewWindow + Write-Output "SCCM client agent uninstall process completed." +} else { + Write-Output "SCCM client agent executable not detected. No action needed." +} From dd632751a15787a0ff684766d893b95a47bdfb36 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:15:56 -0500 Subject: [PATCH 038/107] Add files via upload Escrow BitLocker recovery keys to Azure AD for Azure AD joined or Hybrid joined device. Push with SCCM: Step 1: Prepare the Script Save the provided PowerShell script as a .ps1 file (e.g., UploadBitLockerKey.ps1). Ensure the script is accessible to your SCCM server or stored in a secure network location. Step 2: Create a Script in SCCM Open the Configuration Manager Console. Navigate to Software Library > Scripts. Click on Create Script. Choose PowerShell as the script language. Paste the contents of your UploadBitLockerKey.ps1 script or browse to the file location to import the script. Provide a name for the script and complete the creation wizard. Step 3: Approve the Script Go to Software Library > Scripts. Select the script you just created. Click on Approve/Deny in the ribbon. Approve the script after reviewing any potential security prompts. Step 4: Deploy the Script to a Device Collection Right-click on the approved script and choose Run Script. Select the Device Collection to which you want to deploy the script. Click Next and complete the wizard. Step 5: Monitor the Deployment Go to Monitoring > Script Status. Find the script run you deployed and monitor the results as devices execute the script. Alternative Deployment Method: Compliance Settings You can also use Compliance Settings to deploy the script: Navigate to Assets and Compliance > Compliance Settings > Configuration Items. Create a new Configuration Item and specify the settings. Add the PowerShell script as a Discovery Script or Remediation Script. Create a Configuration Baseline and add the configuration item. Deploy the baseline to a Device Collection. Permissions and Requirements Ensure the user account running the script has permissions to interact with BitLocker and upload keys to Azure AD. Verify that PowerShell script execution is allowed by your client settings in SCCM (PowerShell execution policy should permit script execution). The devices should have the necessary modules, such as MDM, installed if you are leveraging BackupToAAD-BitLockerKeyProtector. --- Escrow-BitLocker_to_AAD.ps1 | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Escrow-BitLocker_to_AAD.ps1 diff --git a/Escrow-BitLocker_to_AAD.ps1 b/Escrow-BitLocker_to_AAD.ps1 new file mode 100644 index 0000000..91d8ecf --- /dev/null +++ b/Escrow-BitLocker_to_AAD.ps1 @@ -0,0 +1,31 @@ +# Check if BitLocker is enabled on the C: drive +$bitLockerStatus = Get-BitLockerVolume -MountPoint C: | Select-Object -ExpandProperty ProtectionStatus + +if ($bitLockerStatus -eq 'On') { + # BitLocker is enabled, check if the recovery key is stored in Azure AD + $recoveryKeyStored = Get-BitLockerVolume -MountPoint C: | Select-Object -ExpandProperty KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'} + + if (-not $recoveryKeyStored) { + # Recovery key is not stored in Azure AD, attempt to upload it + Write-Host "Recovery key not found in Azure AD, attempting to upload..." + + # Retrieve the BitLocker recovery password + $recoveryKey = (Get-BitLockerVolume -MountPoint C:).KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'} | Select-Object -ExpandProperty RecoveryPassword + + if ($recoveryKey) { + # Command to trigger backup of the key to Azure AD (requires appropriate modules and permissions) + try { + BackupToAAD-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId $recoveryKey.KeyProtectorId + Write-Host "BitLocker recovery key successfully uploaded to Azure AD." + } catch { + Write-Host "Failed to upload the BitLocker recovery key to Azure AD. Error: $_" + } + } else { + Write-Host "Unable to retrieve the BitLocker recovery key." + } + } else { + Write-Host "BitLocker recovery key is already stored in Azure AD." + } +} else { + Write-Host "BitLocker is not enabled on the C: drive." +} From 3cf243e83a712f09b3504e23e4839470ec57d8dd Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:30:52 -0500 Subject: [PATCH 039/107] Create Remediate-WiredAutoConfig.ps1 Ensures Wired AutoConfig service is running for Cisco ISE (802.1x) setup. --- Remediate-WiredAutoConfig.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Remediate-WiredAutoConfig.ps1 diff --git a/Remediate-WiredAutoConfig.ps1 b/Remediate-WiredAutoConfig.ps1 new file mode 100644 index 0000000..e9ce060 --- /dev/null +++ b/Remediate-WiredAutoConfig.ps1 @@ -0,0 +1,21 @@ +# Remediation Script: Set Wired AutoConfig service to Automatic and ensure it is running. + +# Get the service status +$service = Get-Service -Name dot3svc -ErrorAction SilentlyContinue + +# Check if the service exists +if ($null -eq $service) { + Write-Output "Service not found. Exiting remediation." + Exit 1 +} + +# Set service to Automatic +Set-Service -Name dot3svc -StartupType Automatic + +# Start the service if it is not running +if ($service.Status -ne 'Running') { + Start-Service -Name dot3svc +} + +Write-Output "Remediation completed: Service set to Automatic and started" +Exit 0 From af5afeb9075fba20f62b09007cff0c56926e85d0 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:32:06 -0500 Subject: [PATCH 040/107] Create Detect-WiredAutoConfig.ps1 Detect Wired AutoConfig service state to ensure service is set to automatic and running for Cisco ISE (802.1x) setup. --- Detect-WiredAutoConfig.ps1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Detect-WiredAutoConfig.ps1 diff --git a/Detect-WiredAutoConfig.ps1 b/Detect-WiredAutoConfig.ps1 new file mode 100644 index 0000000..2b690d4 --- /dev/null +++ b/Detect-WiredAutoConfig.ps1 @@ -0,0 +1,18 @@ +# Detection Script: Check if the Wired AutoConfig service is running and set to Automatic. + +# Get the service status +$service = Get-Service -Name dot3svc -ErrorAction SilentlyContinue + +# Check if the service exists and its configuration +if ($null -eq $service) { + Write-Output "Service not found" + Exit 1 +} + +if ($service.StartType -ne 'Automatic' -or $service.Status -ne 'Running') { + Write-Output "Service is not configured correctly or not running" + Exit 1 +} + +Write-Output "Service is configured correctly and running" +Exit 0 From 94bc2fb958f01afd0995472d816d4cfa9e15bd34 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:21:37 -0500 Subject: [PATCH 041/107] Create Detect-WUServer.ps1 Script pair for cleanup of WSUS registry settings from SCCM or WSUS / GPO --- Detect-WUServer.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Detect-WUServer.ps1 diff --git a/Detect-WUServer.ps1 b/Detect-WUServer.ps1 new file mode 100644 index 0000000..0c49630 --- /dev/null +++ b/Detect-WUServer.ps1 @@ -0,0 +1,13 @@ +# Detection Script +$wsusPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" +$wsusKeys = @("WUServer", "WUStatusServer") + +$foundKeys = $wsusKeys | Where-Object { (Get-ItemProperty -Path $wsusPath -ErrorAction SilentlyContinue).$_ } + +if ($foundKeys) { + Write-Output "WSUS keys found: $($foundKeys -join ', ')" + Exit 1 # Non-zero exit indicates a fix is needed +} else { + Write-Output "No WSUS keys found" + Exit 0 # Zero exit indicates no remediation needed +} From 97bfe830739dc6d392fecec2fce9338dc9e595bb Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:22:34 -0500 Subject: [PATCH 042/107] Create Remediate-WUServer.ps1 Script pair to cleanup SCCM or WSUS/GPO remnants for Intune managed updates. --- Remediate-WUServer.ps1 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Remediate-WUServer.ps1 diff --git a/Remediate-WUServer.ps1 b/Remediate-WUServer.ps1 new file mode 100644 index 0000000..1ca7538 --- /dev/null +++ b/Remediate-WUServer.ps1 @@ -0,0 +1,16 @@ +# Remediation Script +$wsusPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" +$wsusKeys = @("WUServer", "WUStatusServer") + +if (Test-Path $wsusPath) { + $wsusKeys | ForEach-Object { + Remove-ItemProperty -Path $wsusPath -Name $_ -ErrorAction SilentlyContinue + } + Write-Output "WSUS registry keys removed" +} else { + Write-Output "WSUS registry path not found" +} + +# Optionally reset Windows Update service +Stop-Service -Name wuauserv -Force +Start-Service -Name wuauserv From 47b0163508151d9384c0cc5940910c1f8278bc64 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:58:15 -0500 Subject: [PATCH 043/107] Add files via upload Check for last scan and force scan in WSUS --- Check-WSUS_LastOnlineScan.ps1 | 42 +++++++++++++++++++++++++++++++ Remediate-WSUS_LastOnlineScan.ps1 | 33 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 Check-WSUS_LastOnlineScan.ps1 create mode 100644 Remediate-WSUS_LastOnlineScan.ps1 diff --git a/Check-WSUS_LastOnlineScan.ps1 b/Check-WSUS_LastOnlineScan.ps1 new file mode 100644 index 0000000..2c3fe09 --- /dev/null +++ b/Check-WSUS_LastOnlineScan.ps1 @@ -0,0 +1,42 @@ +# Detection Script +$basePath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\LastOnlineScanTimeForAppCategory" + +# Specify the scan threshold in hours (e.g., 24 hours) +$thresholdHours = 24 +$thresholdTime = (Get-Date).AddHours(-$thresholdHours) + +# Check all subkeys under LastOnlineScanTimeForAppCategory +if (Test-Path $basePath) { + $recentScanFound = $false + + # Iterate through all subkeys + Get-ChildItem -Path $basePath | ForEach-Object { + $properties = Get-ItemProperty -Path $_.PSPath + + # Check each property value in the subkey + foreach ($property in $properties.PSObject.Properties) { + try { + $scanTime = [datetime]::Parse($property.Value) + if ($scanTime -ge $thresholdTime) { + Write-Output "Recent scan found: $($scanTime)" + $recentScanFound = $true + } + } catch { + # Ignore properties that cannot be parsed as datetime + Write-Output "Skipping non-datetime value: $($property.Name)" + } + } + } + + # Determine the result based on the scans found + if ($recentScanFound) { + Write-Output "At least one scan occurred within the acceptable time range." + Exit 0 # No remediation needed + } else { + Write-Output "No recent scan found within the acceptable time range." + Exit 1 # Trigger remediation + } +} else { + Write-Output "Registry path not found, assuming no recent scan." + Exit 1 # Trigger remediation +} diff --git a/Remediate-WSUS_LastOnlineScan.ps1 b/Remediate-WSUS_LastOnlineScan.ps1 new file mode 100644 index 0000000..bcc86a4 --- /dev/null +++ b/Remediate-WSUS_LastOnlineScan.ps1 @@ -0,0 +1,33 @@ +# Remediation Script + +# Initiate Windows Update Scan +try { + Write-Output "Initiating Windows Update scan..." + Start-Process -FilePath "usoclient.exe" -ArgumentList "StartScan" -NoNewWindow -Wait + Write-Output "Windows Update scan initiated successfully." +} catch { + Write-Output "Failed to initiate Windows Update scan: $_" +} + +# Log the action to the Event Viewer +$logName = "Application" +$eventSource = "Custom Windows Update" + +# Check if the source exists, and create it if it doesn't +if (-not [System.Diagnostics.EventLog]::SourceExists($eventSource)) { + try { + New-EventLog -LogName $logName -Source $eventSource + Write-Output "Event source '$eventSource' created successfully." + } catch { + Write-Output "Failed to create event source: $_" + Exit 1 + } +} + +# Write the event log entry +try { + Write-EventLog -LogName $logName -Source $eventSource -EntryType Information -EventId 1000 -Message "Forced Windows Update scan initiated by Intune remediation script." + Write-Output "Event log entry created successfully." +} catch { + Write-Output "Failed to write to event log: $_" +} From 886d973a1bd5dec902bfaa10c1c3cbf9e6751503 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:40:10 -0500 Subject: [PATCH 044/107] Add files via upload Intune remediation script pair to cleanup remnants of GPO for Automatic Updates --- Check-AU.ps1 | 10 ++++++++++ Remediate-AU.ps1 | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Check-AU.ps1 create mode 100644 Remediate-AU.ps1 diff --git a/Check-AU.ps1 b/Check-AU.ps1 new file mode 100644 index 0000000..99e5509 --- /dev/null +++ b/Check-AU.ps1 @@ -0,0 +1,10 @@ +# Detection script for Intune Proactive Remediation +$keyPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" + +if (Test-Path -Path $keyPath) { + Write-Output "Key exists" + exit 1 # Indicates a remediation is required +} else { + Write-Output "Key does not exist" + exit 0 # No remediation required +} diff --git a/Remediate-AU.ps1 b/Remediate-AU.ps1 new file mode 100644 index 0000000..e93bf75 --- /dev/null +++ b/Remediate-AU.ps1 @@ -0,0 +1,37 @@ +# Remediation script for Intune Proactive Remediation +$keyPath = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" +$serviceName = "wuauserv" + +# Check if the registry key exists +if (Test-Path -Path $keyPath) { + try { + # Delete the registry key + Remove-Item -Path $keyPath -Recurse -Force + Write-Output "Registry key deleted successfully." + + # Restart the Windows Update service + if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { + Restart-Service -Name $serviceName -Force + Write-Output "Windows Update service restarted successfully." + } else { + Write-Output "Windows Update service not found, no action taken." + } + + exit 0 # Successful remediation + } catch { + Write-Error "Failed to delete the registry key or restart the service: $_" + exit 1 # Remediation failed + } +} else { + Write-Output "Registry key does not exist, no action required." + + # Ensure the service is running even if the key was not found + if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { + Restart-Service -Name $serviceName -Force + Write-Output "Windows Update service restarted successfully." + } else { + Write-Output "Windows Update service not found, no action taken." + } + + exit 0 # Nothing to remediate +} From d1a5d01fdc973939f54d33c875aa5b25224663b2 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:44:38 -0500 Subject: [PATCH 045/107] Create Detect-FixedDrive.ps1 Check for devices with fixed drive. --- Detect-FixedDrive.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Detect-FixedDrive.ps1 diff --git a/Detect-FixedDrive.ps1 b/Detect-FixedDrive.ps1 new file mode 100644 index 0000000..a3d969f --- /dev/null +++ b/Detect-FixedDrive.ps1 @@ -0,0 +1,11 @@ +# Get the fixed drives +$FixedDrives = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" + +# Check if there are any fixed drives +if ($FixedDrives) { + Write-Host "The device has a fixed drive." + exit 1 # Non-zero exit code indicates fixed drives found +} else { + Write-Host "No fixed drive found." + exit 0 # Zero exit code indicates no fixed drives +} From e87c441e567466d955491763e3a430832a8be60a Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:36:30 -0500 Subject: [PATCH 046/107] Detect-FixedDrive.ps1 updated because OS drive was being detected as fixed. --- Detect-FixedDrive.ps1 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Detect-FixedDrive.ps1 b/Detect-FixedDrive.ps1 index a3d969f..6d63849 100644 --- a/Detect-FixedDrive.ps1 +++ b/Detect-FixedDrive.ps1 @@ -1,11 +1,17 @@ # Get the fixed drives $FixedDrives = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" -# Check if there are any fixed drives -if ($FixedDrives) { - Write-Host "The device has a fixed drive." - exit 1 # Non-zero exit code indicates fixed drives found +# Get the OS drive letter +$OSDrive = (Get-WmiObject Win32_OperatingSystem).SystemDrive + +# Filter out the OS drive +$NonOSFixedDrives = $FixedDrives | Where-Object { $_.DeviceID -ne $OSDrive } + +# Check if there are any non-OS fixed drives +if ($NonOSFixedDrives) { + Write-Host "The device has non-OS fixed drives." + exit 1 # Non-zero exit code indicates non-OS fixed drives found } else { - Write-Host "No fixed drive found." - exit 0 # Zero exit code indicates no fixed drives + Write-Host "No non-OS fixed drives found." + exit 0 # Zero exit code indicates no non-OS fixed drives } From 858ff4e92dc3710b936b36ab19e7eb5cd1a48e5d Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:48:55 -0500 Subject: [PATCH 047/107] Check-ADHealth_NEW.ps1 --- Check-ADHealth_NEW.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Check-ADHealth_NEW.ps1 b/Check-ADHealth_NEW.ps1 index e093c36..c829ae5 100644 --- a/Check-ADHealth_NEW.ps1 +++ b/Check-ADHealth_NEW.ps1 @@ -33,7 +33,7 @@ function Check-ADBackup { $backupFilePath = Join-Path -Path $dcPath -ChildPath "ADBackupStatus.txt" $message | Out-File -FilePath $backupFilePath # Write to a DC-specific file } catch { - Handle-Error "Failed to check AD backup status for $DCName: $_" + Handle-Error "Failed to check AD backup status for $DCName $_" } } From 1499fb1edf98d6902dd9203cbbd36bc4fd9bc387 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:09:29 -0500 Subject: [PATCH 048/107] Create Backup-Permissions Backup ownership / permissions. --- Backup-Permissions | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Backup-Permissions diff --git a/Backup-Permissions b/Backup-Permissions new file mode 100644 index 0000000..b962a9b --- /dev/null +++ b/Backup-Permissions @@ -0,0 +1,12 @@ +$FolderPath = "D:\profiles" +$BackupFile = "D:\profile_permissions_backup.csv" + +# Get ACL information for all folders recursively +Get-ChildItem -Path $FolderPath -Directory -Recurse | +ForEach-Object { + $Acl = Get-Acl $_.FullName + $Acl | Select-Object @{Name="Path";Expression={$_.Path}}, + @{Name="Owner";Expression={$_.Owner}}, + @{Name="Access";Expression={$_.Access}} | + Export-Csv -Path $BackupFile -Append -NoTypeInformation +} From cfeba22ae44ea1a4f3d0841bdb754233eedd77d2 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:10:18 -0500 Subject: [PATCH 049/107] Create Change-Ownership.ps1 Change ownership for roaming profiles. --- Change-Ownership.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Change-Ownership.ps1 diff --git a/Change-Ownership.ps1 b/Change-Ownership.ps1 new file mode 100644 index 0000000..a8cda57 --- /dev/null +++ b/Change-Ownership.ps1 @@ -0,0 +1,7 @@ +$AdminAccount = "BUILTIN\Administrators" +$FolderPath = "D:\profiles" + +# Take ownership and grant full control to administrators +takeown /f $FolderPath /r /d y +icacls $FolderPath /setowner $AdminAccount /t /c +icacls $FolderPath /grant $AdminAccount":F" /t /c From 54aeba1eec797406ce161edf61c709e80deeb7f6 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:11:17 -0500 Subject: [PATCH 050/107] Delete Backup-Permissions --- Backup-Permissions | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Backup-Permissions diff --git a/Backup-Permissions b/Backup-Permissions deleted file mode 100644 index b962a9b..0000000 --- a/Backup-Permissions +++ /dev/null @@ -1,12 +0,0 @@ -$FolderPath = "D:\profiles" -$BackupFile = "D:\profile_permissions_backup.csv" - -# Get ACL information for all folders recursively -Get-ChildItem -Path $FolderPath -Directory -Recurse | -ForEach-Object { - $Acl = Get-Acl $_.FullName - $Acl | Select-Object @{Name="Path";Expression={$_.Path}}, - @{Name="Owner";Expression={$_.Owner}}, - @{Name="Access";Expression={$_.Access}} | - Export-Csv -Path $BackupFile -Append -NoTypeInformation -} From 1e6d5d29b9d0a9d59285f4492f3e4231abe68cff Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:12:25 -0500 Subject: [PATCH 051/107] Backup-Permissions.ps1 Backup of permissions / ownership for roaming profiles. --- Backup-Permissions.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Backup-Permissions.ps1 diff --git a/Backup-Permissions.ps1 b/Backup-Permissions.ps1 new file mode 100644 index 0000000..b962a9b --- /dev/null +++ b/Backup-Permissions.ps1 @@ -0,0 +1,12 @@ +$FolderPath = "D:\profiles" +$BackupFile = "D:\profile_permissions_backup.csv" + +# Get ACL information for all folders recursively +Get-ChildItem -Path $FolderPath -Directory -Recurse | +ForEach-Object { + $Acl = Get-Acl $_.FullName + $Acl | Select-Object @{Name="Path";Expression={$_.Path}}, + @{Name="Owner";Expression={$_.Owner}}, + @{Name="Access";Expression={$_.Access}} | + Export-Csv -Path $BackupFile -Append -NoTypeInformation +} From 1f590d54c06878532ad9fbc299820ca714eeeecb Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:29:57 -0500 Subject: [PATCH 052/107] Check-ADHealth.ps1 --- Check-ADHealth.ps1 | 190 +++++++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 86 deletions(-) diff --git a/Check-ADHealth.ps1 b/Check-ADHealth.ps1 index 088776c..c1dc063 100644 --- a/Check-ADHealth.ps1 +++ b/Check-ADHealth.ps1 @@ -1,95 +1,113 @@ -$comp = $env:computername -$org = "Change to Company Name" -$FormatEnumerationLimit = -1 -Import-Module -Name ActiveDirectory -ErrorAction Stop - -Write-Output "Creating directory for $org" -New-Item -Path . -Name $org -ItemType Directory -ErrorAction Continue - - -# Start the transcript with a new file name including "transcript" -$transcriptFileName = ".\$org\${comp}_transcript.txt" - -Start-Transcript -Path $transcriptFileName -ErrorAction Continue - - -# List of Active Directory sites -Write-Output "Collecting list of Active Directory sites" -Get-ADReplicationSite -Filter * | Format-Table -AutoSize | Out-File -FilePath .\$org\Sites.txt - -# List replication information for each domain controller -Write-Output "Collecting replication information for each domain controller" -$DCs = Get-ADDomainController -Filter * -foreach ($DC in $DCs) { - Write-Output "Replication information for $DC" - repadmin /showrepl $DC.HostName | Out-File -FilePath .\$org\ReplicationInfo_$($DC.Name).txt +# Check-ADHealth.ps1 +<# +.SYNOPSIS +PowerShell script for comprehensive Active Directory health monitoring and reporting. + +.DESCRIPTION +This script performs comprehensive diagnostics on Active Directory environment by collecting +critical information from domain controllers, including replication status, FSMO roles, +event logs, and system information. + +.PARAMETER org +Organization name used for creating output directories and file naming. + +.PARAMETER baseDir +Base directory where all output files will be stored. Defaults to D:\Temp. +#> + +param( + [Parameter(Mandatory=$true)] + [string]$org, + [Parameter(Mandatory=$false)] + [string]$baseDir = "D:\Temp" +) + +# Create base directory structure +$orgDir = Join-Path -Path $baseDir -ChildPath $org +if (-not (Test-Path -Path $orgDir)) { + New-Item -Path $orgDir -ItemType Directory -Force } -# List all domain controllers -Write-Output "Collecting list of all domain controllers" -$DCs | Select-Object Name | Out-File -FilePath .\$org\AllDCs.txt - -# Get list of global catalog servers -Write-Output "Collecting list of global catalog servers" -Get-ADDomainController -Filter {IsGlobalCatalog -eq $true} | Select-Object Name | Out-File -FilePath .\$org\GlobalCatalogs.txt - -# Find primary DC and Time Service host -Write-Output "Finding primary DC and Time Service host" -Get-ADDomainController -Discover -Service "PrimaryDC","TimeService" | Out-File -FilePath .\$org\PrimaryDCTimeService.txt - -# Collect HotFix information for each DC -Write-Output "Collecting HotFix information for each DC" -foreach ($DC in $DCs) { - Write-Output "Collecting HotFixes for $($DC.Name)" - $hotfixes = Get-WmiObject -Class Win32_QuickFixEngineering -ComputerName $DC.Name - $hotfixes | Select-Object Description, HotFixID, InstalledOn | Out-File -FilePath .\$org\HotFixes_$($DC.Name).txt +# Function to write logs +function Write-Log { + param([string]$message) + $logPath = Join-Path -Path $orgDir -ChildPath "script_log.txt" + "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')): $message" | Out-File -FilePath $logPath -Append } -# Collect FSMO Roles Information -Write-Output "Collecting FSMO Roles Information" -$forest = Get-ADForest -$domain = Get-ADDomain - -$FSMORoles = @{ - "Schema Master" = $forest.SchemaMaster; - "Domain Naming Master" = $forest.DomainNamingMaster; - "Infrastructure Master" = $domain.InfrastructureMaster; - "RID Master" = $domain.RIDMaster; - "PDC Emulator" = $domain.PDCEmulator; +# Function to handle errors +function Handle-Error { + param($error) + Write-Log "ERROR: $error" } -$FSMORoles | Out-File -FilePath .\$org\FSMORoles.txt - - -# Collect Application and System Log for each DC -foreach ($DC in $DCs) { - Write-Output "Collecting Application and System Log for $DC" - - # Collect and export the Application log - Get-EventLog -LogName application -ComputerName $DC.Name -Newest 5000 | - Select-Object TimeGenerated, Source, EventID, EntryType, Message | - Export-Csv -Path ".\$org\AppLog_$($DC.Name).csv" -NoTypeInformation - - # Collect and export the System log - Get-EventLog -LogName system -ComputerName $DC.Name -Newest 5000 | - Select-Object TimeGenerated, Source, EventID, EntryType, Message | - Export-Csv -Path ".\$org\SysLog_$($DC.Name).csv" -NoTypeInformation +# Start transcript logging +$transcriptPath = Join-Path -Path $orgDir -ChildPath "transcript.log" +Start-Transcript -Path $transcriptPath + +try { + # Get all Domain Controllers + Write-Log "Getting Domain Controllers" + $DCs = Get-ADDomainController -Filter * + + foreach ($DC in $DCs) { + # Create DC-specific directory + $dcDir = Join-Path -Path $orgDir -ChildPath $DC.Name + if (-not (Test-Path -Path $dcDir)) { + New-Item -Path $dcDir -ItemType Directory -Force + } + + # Collect DC specific information + Write-Log "Collecting information for $($DC.Name)" + + # System Information + Write-Log "Getting system information for $($DC.Name)" + $sysInfoPath = Join-Path -Path $dcDir -ChildPath "SystemInfo.txt" + systeminfo /S $DC.Name > $sysInfoPath + + # Event Logs + Write-Log "Getting event logs for $($DC.Name)" + $appLogPath = Join-Path -Path $dcDir -ChildPath "AppLog.csv" + $sysLogPath = Join-Path -Path $dcDir -ChildPath "SysLog.csv" + Get-EventLog -LogName Application -Newest 5000 -ComputerName $DC.Name | Export-Csv -Path $appLogPath -NoTypeInformation + Get-EventLog -LogName System -Newest 5000 -ComputerName $DC.Name | Export-Csv -Path $sysLogPath -NoTypeInformation + + # Hotfixes + Write-Log "Getting hotfixes for $($DC.Name)" + $hotfixPath = Join-Path -Path $dcDir -ChildPath "Hotfixes.csv" + Get-HotFix -ComputerName $DC.Name | Export-Csv -Path $hotfixPath -NoTypeInformation + + # Netlogon log + Write-Log "Copying netlogon.log for $($DC.Name)" + $remoteNetlogonPath = "\\$($DC.Name)\c$\windows\debug\netlogon.log" + $localNetlogonPath = Join-Path -Path $dcDir -ChildPath "netlogon.log" + Copy-Item -Path $remoteNetlogonPath -Destination $localNetlogonPath -ErrorAction Continue + } + + # Collect AD-wide information + Write-Log "Collecting AD-wide information" + + # FSMO Roles + Write-Log "Getting FSMO roles" + $fsmoPath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" + netdom query fsmo > $fsmoPath + + # Replication Status + Write-Log "Getting replication status" + $replPath = Join-Path -Path $orgDir -ChildPath "ReplicationStatus.txt" + repadmin /showrepl * /csv > $replPath + + # Sites and Services + Write-Log "Getting sites and services information" + $sitesPath = Join-Path -Path $orgDir -ChildPath "ADSites.txt" + nltest /server:$env:COMPUTERNAME /dsgetsite > $sitesPath + + Write-Log "Script completed successfully" } - - -# Create computer subdirectory for each DC -foreach ($DC in $DCs) { - Write-Output "Creating subdirectory for $DC" - New-Item -Path .\$org -Name $DC.Name -ItemType Directory +catch { + Handle-Error $_ + Write-Log "Script terminated with errors" } - -# Copy netlogon.log to each DC's subdirectory -foreach ($DC in $DCs) { - Write-Output "Copying netlogon.log for $($DC.Name)" - $remoteNetlogonPath = "\\" + $DC.Name + "\c$\windows\debug\netlogon.log" - $localNetlogonPath = ".\$org\$($DC.Name)\netlogon.log" - Copy-Item -Path $remoteNetlogonPath -Destination $localNetlogonPath -ErrorAction Continue +finally { + Stop-Transcript } - - -Stop-Transcript From d2583d5d872ceadaf2bb08dfc483fd587af90e95 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:41:34 -0500 Subject: [PATCH 053/107] Add files via upload Configure Automatic Timezone Selection for Intune or GPO --- Set-AutomaticTimezone.ps1 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Set-AutomaticTimezone.ps1 diff --git a/Set-AutomaticTimezone.ps1 b/Set-AutomaticTimezone.ps1 new file mode 100644 index 0000000..5eac556 --- /dev/null +++ b/Set-AutomaticTimezone.ps1 @@ -0,0 +1,2 @@ +Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\location -Name Value -Value "Allow" +Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\tzautoupdate -Name start -Value "3" \ No newline at end of file From 08dee6f0d0d953f539fe5f850b1c7b63c8e87457 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:36:54 -0500 Subject: [PATCH 054/107] Add files via upload Carbon Black Detection and Removal --- Detect-CarbonBlack.ps1 | 18 ++++++++++++++++++ Remediate-CarbonBlack.ps1 | 31 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Detect-CarbonBlack.ps1 create mode 100644 Remediate-CarbonBlack.ps1 diff --git a/Detect-CarbonBlack.ps1 b/Detect-CarbonBlack.ps1 new file mode 100644 index 0000000..3e7a3c2 --- /dev/null +++ b/Detect-CarbonBlack.ps1 @@ -0,0 +1,18 @@ +try { + $cbService = Get-Service -Name "CbDefense" -ErrorAction SilentlyContinue + $cbPath = "C:\Program Files\CarbonBlack\CbDefense" + $cbReg = Get-ItemProperty -Path "HKLM:\SOFTWARE\CarbonBlack" -ErrorAction SilentlyContinue + + if ($cbService -or (Test-Path $cbPath) -or $cbReg) { + # Carbon Black found, return 1 to trigger remediation + Write-Output "Carbon Black components detected" + Exit 1 + } else { + # No Carbon Black found, return 0 + Write-Output "No Carbon Black components found" + Exit 0 + } +} catch { + Write-Error "Error during Carbon Black detection: $_" + Exit 1 +} \ No newline at end of file diff --git a/Remediate-CarbonBlack.ps1 b/Remediate-CarbonBlack.ps1 new file mode 100644 index 0000000..dc522d8 --- /dev/null +++ b/Remediate-CarbonBlack.ps1 @@ -0,0 +1,31 @@ +try { + # Stop Carbon Black services + Stop-Service -Name "CbDefense" -Force -ErrorAction SilentlyContinue + Stop-Service -Name "CbDefenseSensor" -Force -ErrorAction SilentlyContinue + + # Uninstall Carbon Black using uninstall code + $cbPath = "C:\Program Files\CarbonBlack\CbDefense\" + $uninstallCode = "YOUR-UNINSTALL-CODE-HERE" # Replace with actual uninstall code + $uninstallCmd = "$cbPath\cbuninstall.exe /quiet /norestart /CODE=$uninstallCode" + + if (Test-Path "$cbPath\cbuninstall.exe") { + $process = Start-Process -FilePath "cmd.exe" -ArgumentList "/c $uninstallCmd" -Wait -NoNewWindow -PassThru + if ($process.ExitCode -ne 0) { + throw "Uninstall process failed with exit code: $($process.ExitCode)" + } + } else { + throw "Carbon Black uninstaller not found" + } + + # Clean up registry keys + Remove-Item -Path "HKLM:\SOFTWARE\CarbonBlack" -Recurse -Force -ErrorAction SilentlyContinue + + # Clean up leftover files + Remove-Item -Path "C:\Program Files\CarbonBlack" -Recurse -Force -ErrorAction SilentlyContinue + + Write-Output "Carbon Black removal completed successfully" + Exit 0 +} catch { + Write-Error "Error during Carbon Black removal: $_" + Exit 1 +} \ No newline at end of file From 2a7b78f883267d68ed530420af52fbe2cc517cf8 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:38:32 -0500 Subject: [PATCH 055/107] Remediate-CarbonBlack.ps1 Adjusted uninstall switch --- Remediate-CarbonBlack.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Remediate-CarbonBlack.ps1 b/Remediate-CarbonBlack.ps1 index dc522d8..40330be 100644 --- a/Remediate-CarbonBlack.ps1 +++ b/Remediate-CarbonBlack.ps1 @@ -6,7 +6,7 @@ try { # Uninstall Carbon Black using uninstall code $cbPath = "C:\Program Files\CarbonBlack\CbDefense\" $uninstallCode = "YOUR-UNINSTALL-CODE-HERE" # Replace with actual uninstall code - $uninstallCmd = "$cbPath\cbuninstall.exe /quiet /norestart /CODE=$uninstallCode" + $uninstallCmd = "$cbPath\cbuninstall.exe /quiet /norestart /uninstall $uninstallCode" if (Test-Path "$cbPath\cbuninstall.exe") { $process = Start-Process -FilePath "cmd.exe" -ArgumentList "/c $uninstallCmd" -Wait -NoNewWindow -PassThru @@ -28,4 +28,4 @@ try { } catch { Write-Error "Error during Carbon Black removal: $_" Exit 1 -} \ No newline at end of file +} From 51e0380266fcb5ba5c36f342e861c6d109f11137 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:46:30 -0500 Subject: [PATCH 056/107] Discover-Printers_CIM_WMI.ps1 --- Discover-Printers_CIM_WMI.ps1 | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Discover-Printers_CIM_WMI.ps1 diff --git a/Discover-Printers_CIM_WMI.ps1 b/Discover-Printers_CIM_WMI.ps1 new file mode 100644 index 0000000..6766597 --- /dev/null +++ b/Discover-Printers_CIM_WMI.ps1 @@ -0,0 +1,43 @@ +# Requires the ActiveDirectory module for Get-ADComputer (part of RSAT or installed on a domain-joined server). +# Adjust the -Filter and -SearchBase parameters as needed. + +# 1. Gather a list of computers (example: all Windows computers in a specific OU). +$computers = Get-ADComputer -Filter "OperatingSystem -like 'Windows*'" -SearchBase "OU=Computers,DC=Contoso,DC=com" | + Select-Object -ExpandProperty Name + +# 2. Prepare a collection to store results. +$printerInventory = New-Object System.Collections.Generic.List[Object] + +foreach ($computer in $computers) { + Write-Host "Querying printers on $computer..." + + try { + # Using WMI (DCOM) approach + # Alternatively, you can use Get-CimInstance with -Authentication and -ComputerName if you prefer: + # $printers = Get-CimInstance -ClassName Win32_Printer -ComputerName $computer + + $printers = Get-WmiObject -Class Win32_Printer -ComputerName $computer -ErrorAction Stop + + foreach ($printer in $printers) { + $printerInfo = [PSCustomObject]@{ + ComputerName = $computer + PrinterName = $printer.Name + DriverName = $printer.DriverName + PortName = $printer.PortName + ShareName = $printer.ShareName + SystemName = $printer.SystemName + Default = $printer.Default + Network = $printer.Network + } + $printerInventory.Add($printerInfo) + } + } + catch { + Write-Warning "Failed to connect or query $computer. Error: $_" + } +} + +# 3. Export results to CSV +$exportPath = "C:\Temp\AllPrintersInventory.csv" +$printerInventory | Export-Csv -Path $exportPath -NoTypeInformation +Write-Host "Printer inventory exported to $exportPath" From 52c503969e6e67577e297c7c632b6ef0668e77c0 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:47:47 -0500 Subject: [PATCH 057/107] Discover-Printer_PS.ps1 --- Discover-Printer_PS.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Discover-Printer_PS.ps1 diff --git a/Discover-Printer_PS.ps1 b/Discover-Printer_PS.ps1 new file mode 100644 index 0000000..c050c8a --- /dev/null +++ b/Discover-Printer_PS.ps1 @@ -0,0 +1,12 @@ +$computers = Get-ADComputer -Filter "OperatingSystem -like 'Windows*'" | + Select-Object -ExpandProperty Name + +foreach ($computer in $computers) { + try { + $printers = Get-Printer -ComputerName $computer -ErrorAction Stop + # Process or export $printers as needed + } + catch { + Write-Warning "Failed to query printers on $computer. Error: $_" + } +} From fe8ceff744e08dd5d993788a6e2fca8b8250f93a Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:48:31 -0500 Subject: [PATCH 058/107] Rename Discover-Printer_PS.ps1 to Discover-Printers_PS.ps1 --- Discover-Printer_PS.ps1 => Discover-Printers_PS.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Discover-Printer_PS.ps1 => Discover-Printers_PS.ps1 (100%) diff --git a/Discover-Printer_PS.ps1 b/Discover-Printers_PS.ps1 similarity index 100% rename from Discover-Printer_PS.ps1 rename to Discover-Printers_PS.ps1 From fc6ae1593d5dd2351ff3f42e33de334d61acba33 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:50:02 -0500 Subject: [PATCH 059/107] Add files via upload Forces Intune devices to synchronize with the Entra/Intune services. --- Force-Sync.ps1 | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Force-Sync.ps1 diff --git a/Force-Sync.ps1 b/Force-Sync.ps1 new file mode 100644 index 0000000..24c1711 --- /dev/null +++ b/Force-Sync.ps1 @@ -0,0 +1,30 @@ +Install-Module -Name Microsoft.Graph.DeviceManagement.Actions -Force -AllowClobber + +Install-Module -Name Microsoft.Graph.DeviceManagement -Force -AllowClobber + +# Importing the SDK Module + +Import-Module -Name Microsoft.Graph.DeviceManagement.Actions + +Connect-MgGraph -scope DeviceManagementManagedDevices.PrivilegedOperations.All, DeviceManagementManagedDevices.ReadWrite.All,DeviceManagementManagedDevices.Read.All + +#### Gets All devices + +$Devices = Get-MgDeviceManagementManagedDevice -All + +Foreach ($Device in $Devices) + +{ + +Sync-MgDeviceManagementManagedDevice -ManagedDeviceId $Device.Id + +Write-Host "Sending Sync request to Device with Device name $($Device.DeviceName)" -ForegroundColor Yellow + +} + +Disconnect-Graph + +# Device = Get-MgDeviceManagementManagedDevice -Filter "contains(deviceName,'VIA9999')" + +# Sync-MgDeviceManagementManagedDevice -ManagedDeviceId $device.Id + \ No newline at end of file From 0492bc3ef8ba631b285bf97ffb3d4bb49e74e3f8 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 26 Feb 2025 10:55:28 -0500 Subject: [PATCH 060/107] Create Exchange-MailboxRetentionPolicyManager.ps1 This PowerShell script manages Exchange Online mailbox retention policies, specifically excluding Notes and Tasks folders from archiving. Key features include: - Batch processing of user mailboxes with rate limiting - Comprehensive error handling and logging - Built-in backup functionality for retention policies - WhatIf parameter for testing changes - Progress tracking and verification steps The script creates custom retention policies that exclude Notes and Tasks folders, maintaining all other retention settings. It includes user confirmation prompts and detailed logging for audit purposes. Requirements: - Exchange Online PowerShell module - Appropriate Exchange Online admin permissions - PowerShell 5.1 or higher --- Exchange-MailboxRetentionPolicyManager.ps1 | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 Exchange-MailboxRetentionPolicyManager.ps1 diff --git a/Exchange-MailboxRetentionPolicyManager.ps1 b/Exchange-MailboxRetentionPolicyManager.ps1 new file mode 100644 index 0000000..5ca543d --- /dev/null +++ b/Exchange-MailboxRetentionPolicyManager.ps1 @@ -0,0 +1,112 @@ +# Script parameters +param( + [Parameter(Mandatory=$true)] + [string]$UserPrincipalName, + [switch]$WhatIf +) + +# Function for logging +function Write-Log { + param($Message) + $logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'): $Message" + Write-Host $logMessage + Add-Content -Path "RetentionPolicy_Changes.log" -Value $logMessage +} + +try { + # Connect to Exchange Online with error handling + Write-Log "Attempting to connect to Exchange Online..." + Connect-ExchangeOnline -UserPrincipalName $UserPrincipalName -ErrorAction Stop + + # Get all user mailboxes with rate limiting + Write-Log "Retrieving mailboxes..." + $Mailboxes = Get-Mailbox -RecipientTypeDetails UserMailbox + $totalMailboxes = $Mailboxes.Count + $counter = 0 + + foreach ($Mailbox in $Mailboxes) { + $counter++ + $MailboxIdentity = $Mailbox.PrimarySmtpAddress + Write-Log "Processing mailbox ($counter/$totalMailboxes): $MailboxIdentity" + + try { + # Get the mailbox retention policy + $RetentionPolicy = Get-Mailbox $MailboxIdentity | + Select-Object -ExpandProperty RetentionPolicy + + if ($RetentionPolicy) { + Write-Log "Current Retention Policy: $RetentionPolicy" + + # Get the retention tags with error handling + $RetentionTags = Get-RetentionPolicy $RetentionPolicy -ErrorAction Stop | + Select-Object -ExpandProperty RetentionPolicyTagLinks + + # Filter out Notes and Tasks tags + $UpdatedTags = @() + foreach ($Tag in $RetentionTags) { + $TagDetails = Get-RetentionPolicyTag $Tag + if ($TagDetails.Type -ne "Notes" -and $TagDetails.Type -ne "Tasks") { + $UpdatedTags += $TagDetails.Name + } else { + Write-Log "Excluding tag: $($TagDetails.Name)" + } + } + + # Create a new retention policy without Notes and Tasks + $NewPolicyName = "$RetentionPolicy - No Notes/Tasks" + if (-not (Get-RetentionPolicy -Identity $NewPolicyName -ErrorAction SilentlyContinue)) { + if ($WhatIf) { + Write-Log "WhatIf: Would create new policy: $NewPolicyName" + } else { + New-RetentionPolicy -Name $NewPolicyName -RetentionPolicyTagLinks $UpdatedTags + Write-Log "Created new policy: $NewPolicyName" + } + } + + # Backup current policy settings + $BackupFile = "RetentionPolicy_Backup_$(Get-Date -Format 'yyyyMMdd').json" + $CurrentSettings = Get-Mailbox $MailboxIdentity | + Select-Object PrimarySmtpAddress, RetentionPolicy + $CurrentSettings | ConvertTo-Json | Add-Content -Path $BackupFile + + # Assign the new retention policy with confirmation + if ($WhatIf) { + Write-Log "WhatIf: Would update mailbox $MailboxIdentity with policy $NewPolicyName" + } else { + $confirmation = Read-Host "Update retention policy for $MailboxIdentity? (Y/N)" + if ($confirmation -eq 'Y') { + Set-Mailbox $MailboxIdentity -RetentionPolicy $NewPolicyName + Write-Log "Updated mailbox $MailboxIdentity with retention policy $NewPolicyName" + + # Verify the change + $verificationPolicy = (Get-Mailbox $MailboxIdentity).RetentionPolicy + if ($verificationPolicy -eq $NewPolicyName) { + Write-Log "Verification successful for $MailboxIdentity" + } else { + Write-Log "WARNING: Verification failed for $MailboxIdentity" + } + } else { + Write-Log "Skipped updating $MailboxIdentity based on user input" + } + } + } else { + Write-Log "No retention policy found for $MailboxIdentity" + } + + # Add delay to avoid throttling + Start-Sleep -Milliseconds 500 + + } catch { + Write-Log "ERROR processing $MailboxIdentity: $($_.Exception.Message)" + continue + } + } + +} catch { + Write-Log "CRITICAL ERROR: $($_.Exception.Message)" +} finally { + # Disconnect from Exchange Online + Write-Log "Disconnecting from Exchange Online..." + Disconnect-ExchangeOnline -Confirm:$false + Write-Log "Script execution completed" +} From 25218f9b9e57a58b55589c96a212511ce65c08cc Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:11:25 -0400 Subject: [PATCH 061/107] Create Clean-IntuneEnrollment.ps1 Use on devices that failed Intune automatic enrollment due to previous enrollments. --- Clean-IntuneEnrollment.ps1 | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Clean-IntuneEnrollment.ps1 diff --git a/Clean-IntuneEnrollment.ps1 b/Clean-IntuneEnrollment.ps1 new file mode 100644 index 0000000..ea0196f --- /dev/null +++ b/Clean-IntuneEnrollment.ps1 @@ -0,0 +1,30 @@ +# Script to clean up Intune enrollment artifacts +# Run with elevated permissions + +# Stop Intune Management Extension service +Stop-Service -Name IntuneManagementExtension -Force + +# Remove Intune Management Extension directory +Remove-Item -Path "C:\Program Files (x86)\Microsoft Intune Management Extension" -Recurse -Force -ErrorAction SilentlyContinue + +# Clean up registry keys +$registryPaths = @( + "HKLM:\SOFTWARE\Microsoft\Enrollments\*", + "HKLM:\SOFTWARE\Microsoft\Enrollments\Status\*", + "HKLM:\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked\*", + "HKLM:\SOFTWARE\Microsoft\PolicyManager\AdmxInstalled\*", + "HKLM:\SOFTWARE\Microsoft\PolicyManager\Providers\*", + "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\*", + "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Logger\*", + "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Sessions\*" +) + +foreach ($path in $registryPaths) { + Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue +} + +# Restart device enrollment service +Restart-Service -Name DeviceEnrollmentService + +# Re-run device enrollment +Start-Process -FilePath "C:\Windows\System32\deviceenroller.exe" -ArgumentList "/c /AutoEnrollMDM" -Wait From 39437714ed794b16612ffa9f430da2bcd68cd808 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:00:34 -0400 Subject: [PATCH 062/107] Create Set-EnrollmentAttribute.ps1 Used to push software after a device has been enrolled for five days. ExtensionAttribute15 (last one available) is set with the value DelaySoftwarePush after five days have passed. This attribute is used to place devices into a dynamic group that is assigned to a specific software deployment. --- Set-EnrollmentAttribute.ps1 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Set-EnrollmentAttribute.ps1 diff --git a/Set-EnrollmentAttribute.ps1 b/Set-EnrollmentAttribute.ps1 new file mode 100644 index 0000000..9474b29 --- /dev/null +++ b/Set-EnrollmentAttribute.ps1 @@ -0,0 +1,20 @@ +# Connect to Microsoft Graph + +Connect-MgGraph -Scopes "DeviceManagementManagedDevices.ReadWrite.All", "Directory.ReadWrite.All" + +# Get all Intune devices +$devices = Get-MgDeviceManagementManagedDevice -All + +#Current date +$currentDate = Get-Date + +foreach ($device in $devices) { +$enrollDate = [DateTime]$device.EnrolledDateTime +$daysSinceEnrolled = ($currentDate - $enrollDate).Days +if ($daysSinceEnrolled -ge 5) { + # Update Azure AD device object with custom attribute + $deviceObjectId = $device.AzureAdDeviceId + Update-MgDevice -DeviceId $deviceObjectId -ExtensionAttributes @{ "extensionAttribute15" = "DelaySoftwarePush" } + Write-Host "Tagged device: $($device.DeviceName) for software push." +} +} From 70bdebedeca0536ca30ea6969e868298f0d12fdc Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:34:50 -0400 Subject: [PATCH 063/107] Create Set-EnrollmentAttribute_RANGE.ps1 Used to push software after a device has been enrolled for five days. ExtensionAttribute15 (last one available) is set with the value DelaySoftwarePush after five days have passed. This attribute is used to place devices into a dynamic group that is assigned to a specific software deployment. This script will also REMOVE the attribute after 15 days, thereby removing the device from the dynamic group. --- Set-EnrollmentAttribute_RANGE.ps1 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Set-EnrollmentAttribute_RANGE.ps1 diff --git a/Set-EnrollmentAttribute_RANGE.ps1 b/Set-EnrollmentAttribute_RANGE.ps1 new file mode 100644 index 0000000..0316670 --- /dev/null +++ b/Set-EnrollmentAttribute_RANGE.ps1 @@ -0,0 +1,25 @@ +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "DeviceManagementManagedDevices.ReadWrite.All", "Directory.ReadWrite.All" + +# Get all Intune devices +$devices = Get-MgDeviceManagementManagedDevice -All + +# Current date +$currentDate = Get-Date + +foreach ($device in $devices) { + $enrollDate = [DateTime]$device.EnrolledDateTime + $daysSinceEnrolled = ($currentDate - $enrollDate).Days + $deviceObjectId = $device.AzureAdDeviceId + + if ($daysSinceEnrolled -ge 5 -and $daysSinceEnrolled -le 15) { + # Set the attribute for devices enrolled between 5 and 15 days ago + Update-MgDevice -DeviceId $deviceObjectId -ExtensionAttributes @{ "extensionAttribute1" = "DelaySoftwarePush" } + Write-Host "Tagged device: $($device.DeviceName) for software push." + } + elseif ($daysSinceEnrolled -gt 15) { + # Remove the attribute after 15 days + Update-MgDevice -DeviceId $deviceObjectId -ExtensionAttributes @{ "extensionAttribute1" = $null } + Write-Host "Removed tag from device: $($device.DeviceName)." + } +} From 926c2f40a0e27f7bd4fc1496878eb1a75b6affdd Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 11 Apr 2025 17:32:36 -0400 Subject: [PATCH 064/107] Create Check-ADHealth_Updated.ps1 --- Check-ADHealth_Updated.ps1 | 234 +++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 Check-ADHealth_Updated.ps1 diff --git a/Check-ADHealth_Updated.ps1 b/Check-ADHealth_Updated.ps1 new file mode 100644 index 0000000..23ebeb9 --- /dev/null +++ b/Check-ADHealth_Updated.ps1 @@ -0,0 +1,234 @@ +<# +.SYNOPSIS + Gathers AD-related data and stores the output in the specified directory structure. + +.DESCRIPTION + By default, this script logs to "C:\Temp\YourCompanyName". You can override these + defaults by specifying -BaseDir and -Org parameters. + +.PARAMETER BaseDir + The parent directory under which all files will be stored. + +.PARAMETER Org + The organization or folder name used under BaseDir. + +.EXAMPLE + .\Collect-ADInfo.ps1 + + Uses the default values for BaseDir and Org. + +.EXAMPLE + .\Collect-ADInfo.ps1 -BaseDir "D:\Temp" -Org "MyCo" + + Writes all logs and data to D:\Temp\MyCo. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $false)] + [string]$BaseDir = "C:\Temp", + + [Parameter(Mandatory = $false)] + [string]$Org = "YourCompanyName" +) + +# Build our working directory from the parameters +$orgDir = Join-Path -Path $BaseDir -ChildPath $Org + +# A function for logging +function Write-Log { + param([string]$message) + $logPath = Join-Path -Path $orgDir -ChildPath "script_log.txt" + "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')): $message" | Out-File -FilePath $logPath -Append +} + +# A function for handling errors +function Handle-Error { + param($error) + Write-Log "ERROR: $error" +} + +function Check-ADBackup { + param( + [string]$DCName, + [string]$dcPath + ) + try { + # Attempt to retrieve the 'lastBackup' property + $backupCheck = Get-ADObject -Identity "CN=NTDS Settings,CN=$DCName,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,$((Get-ADDomain).DistinguishedName)" -Properties lastBackup -Server $DCName -ErrorAction Stop + + if ($backupCheck.PSObject.Properties.Name -contains 'lastBackup') { + if ($backupCheck.lastBackup) { + $lastBackupTime = [DateTime]::FromFileTime($backupCheck.lastBackup) + $message = "Last backup of AD on DC $DCName was at $lastBackupTime" + } else { + $message = "No backup information found for AD on DC $DCName" + } + } + else { + $message = "lastBackup attribute is not present on DC $DCName." + } + + Write-Log $message + $backupFilePath = Join-Path -Path $dcPath -ChildPath "ADBackupStatus.txt" + $message | Out-File -FilePath $backupFilePath + } + catch { + Handle-Error "Failed to check AD backup status for $DCName $_" + } +} + +# Ensure base and organization directories exist +if (-not (Test-Path -Path $BaseDir)) { + New-Item -Path $BaseDir -ItemType Directory -Force | Out-Null + Write-Log "Created base directory: $BaseDir" +} +if (-not (Test-Path -Path $orgDir)) { + New-Item -Path $orgDir -ItemType Directory -Force | Out-Null + Write-Log "Created organization directory: $orgDir" +} + +# Start transcript +$comp = $env:computername +$transcriptFileName = Join-Path -Path $orgDir -ChildPath "${comp}_transcript.txt" +Start-Transcript -Path $transcriptFileName -ErrorAction Continue +Write-Log "Started transcript at $transcriptFileName" + +# Collect FSMO Roles +try { + $forest = Get-ADForest + $domain = Get-ADDomain + + $FSMORoles = @{ + "Schema Master" = $forest.SchemaMaster + "Domain Naming Master" = $forest.DomainNamingMaster + "Infrastructure Master" = $domain.InfrastructureMaster + "RID Master" = $domain.RIDMaster + "PDC Emulator" = $domain.PDCEmulator + } + + $FSMORolesFilePath = Join-Path -Path $orgDir -ChildPath "FSMORoles.txt" + $FSMORoles | Out-File -FilePath $FSMORolesFilePath + Write-Log "FSMO roles collected and written to $FSMORolesFilePath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Collect AD Sites Information +try { + $sitesInfo = Get-ADReplicationSite -Filter * | Format-Table -AutoSize + $sitesFilePath = Join-Path -Path $orgDir -ChildPath "ADSites.txt" + $sitesInfo | Out-File -FilePath $sitesFilePath + Write-Log "Active Directory sites information collected and written to $sitesFilePath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Collect Global Catalog Servers +try { + $gcs = Get-ADDomainController -Filter {IsGlobalCatalog -eq $true} | Select-Object Name, HostName, Site + $gcsFilePath = Join-Path -Path $orgDir -ChildPath "GlobalCatalogs.txt" + $gcs | Out-File -FilePath $gcsFilePath + Write-Log "Global Catalog servers information collected and written to $gcsFilePath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Collect DC List +try { + $DCs = Get-ADDomainController -Filter * + $DCsFilePath = Join-Path -Path $orgDir -ChildPath "DomainControllers.txt" + $DCs | Select-Object Name, HostName, Site | Out-File -FilePath $DCsFilePath + Write-Log "Domain controllers list collected and written to $DCsFilePath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Collect Forest and Domain Functional Levels +try { + $forestLevel = (Get-ADForest).ForestMode + $domainLevel = (Get-ADDomain).DomainMode + + # Option A: Write both to one file if that's all you need + # "Forest Functional Level: $forestLevel`nDomain Functional Level: $domainLevel" | Out-File (Join-Path $orgDir "FunctionalLevels.txt") + + # Option B: Write them separately if your report script checks these files individually + $forestLevelPath = Join-Path -Path $orgDir -ChildPath "ForestLevel.txt" + $domainLevelPath = Join-Path -Path $orgDir -ChildPath "DomainLevel.txt" + + "Forest Functional Level: $forestLevel" | Out-File -FilePath $forestLevelPath + "Domain Functional Level: $domainLevel" | Out-File -FilePath $domainLevelPath + + Write-Log "Forest level written to $forestLevelPath" + Write-Log "Domain level written to $domainLevelPath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Collect Active Directory Schema Version +try { + $schema = Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion + $schemaVersion = $schema.objectVersion + $schemaVersionPath = Join-Path -Path $orgDir -ChildPath "SchemaVersion.txt" + "Active Directory Schema Version: $schemaVersion" | Out-File -FilePath $schemaVersionPath + Write-Log "Schema version collected and written to $schemaVersionPath" +} +catch { + Handle-Error $_.Exception.Message +} + +# Operations for each Domain Controller +foreach ($DC in $DCs) { + $dcPath = Join-Path -Path $orgDir -ChildPath ($DC.Name -replace "[^a-zA-Z0-9]", "_") + + # Ensure directory for each DC + if (-not (Test-Path -Path $dcPath)) { + New-Item -Path $dcPath -ItemType Directory -Force | Out-Null + Write-Log "Created directory for DC: $dcPath" + } + + try { + # Replication info + $replicationFilePath = Join-Path -Path $dcPath -ChildPath "replication_info.txt" + repadmin /showrepl $DC.HostName | Out-File -FilePath $replicationFilePath + Write-Log "Replication info for $($DC.Name) written to $replicationFilePath" + + # HotFix info + $hotFixFilePath = Join-Path -Path $dcPath -ChildPath "hotfixes.txt" + Get-HotFix -ComputerName $DC.Name | Format-List | Out-File -FilePath $hotFixFilePath + Write-Log "Hotfix info for $($DC.Name) written to $hotFixFilePath" + + # Check AD backup status + Check-ADBackup -DCName $DC.Name -dcPath $dcPath + + # Application and System logs + $appLogPath = Join-Path -Path $dcPath -ChildPath "app_log.csv" + Get-EventLog -LogName Application -Newest 50 -ComputerName $DC.Name | + Select-Object TimeGenerated, Source, InstanceID, Message | + Export-Csv -Path $appLogPath -NoTypeInformation + + $sysLogPath = Join-Path -Path $dcPath -ChildPath "sys_log.csv" + Get-EventLog -LogName System -Newest 50 -ComputerName $DC.Name | + Select-Object TimeGenerated, Source, InstanceID, Message | + Export-Csv -Path $sysLogPath -NoTypeInformation + + Write-Log "Application and system logs for $($DC.Name) written to $appLogPath and $sysLogPath" + + # Copy netlogon.log + $netlogonSource = "\\$($DC.HostName)\c$\Windows\debug\netlogon.log" + $netlogonDest = Join-Path -Path $dcPath -ChildPath "netlogon.log" + Copy-Item -Path $netlogonSource -Destination $netlogonDest -ErrorAction Continue + Write-Log "Netlogon log for $($DC.Name) copied to $netlogonDest" + } + catch { + Handle-Error $_.Exception.Message + } +} + +Stop-Transcript +Write-Log "Script execution completed and transcript stopped." From 39d12f1ad2b6010ade5f22df83f0c0c32d7bb760 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:52:29 -0400 Subject: [PATCH 065/107] Add files via upload Builds an Intune application packaging environment by creating typical folders used and by also downloading the Intune App Packager. --- Build-IntuneAppBuilder.ps1 | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Build-IntuneAppBuilder.ps1 diff --git a/Build-IntuneAppBuilder.ps1 b/Build-IntuneAppBuilder.ps1 new file mode 100644 index 0000000..82ed6bd --- /dev/null +++ b/Build-IntuneAppBuilder.ps1 @@ -0,0 +1,33 @@ +# Define the base path (current location or customize) +$basePath = Join-Path -Path $PSScriptRoot -ChildPath "App" + +# Define folder structure +$folders = @( + $basePath, + "$basePath\Intune", + "$basePath\Source", + "$basePath\Detection", + "$basePath\Output" +) + +# Create folders if they don't exist +foreach ($folder in $folders) { + if (-not (Test-Path -Path $folder)) { + New-Item -Path $folder -ItemType Directory | Out-Null + Write-Host "Created folder: $folder" + } else { + Write-Host "Folder already exists: $folder" + } +} + +# Download IntuneWinAppUtil +$downloadUrl = "https://aka.ms/intunewinapputil" +$destination = Join-Path -Path $basePath -ChildPath "IntuneWinAppUtil.exe" + +try { + Write-Host "Downloading IntuneWinAppUtil.exe..." + Invoke-WebRequest -Uri $downloadUrl -OutFile $destination -UseBasicParsing + Write-Host "Download complete: $destination" +} catch { + Write-Warning "Failed to download IntuneWinAppUtil.exe: $_" +} From e862caa47122297e8888935be494f0e545d78cfc Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:01:39 -0400 Subject: [PATCH 066/107] Build-IntuneAppBuilder.ps1 --- Build-IntuneAppBuilder.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build-IntuneAppBuilder.ps1 b/Build-IntuneAppBuilder.ps1 index 82ed6bd..5423fa7 100644 --- a/Build-IntuneAppBuilder.ps1 +++ b/Build-IntuneAppBuilder.ps1 @@ -21,7 +21,7 @@ foreach ($folder in $folders) { } # Download IntuneWinAppUtil -$downloadUrl = "https://aka.ms/intunewinapputil" +$downloadUrl = "https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool/blob/master/IntuneWinAppUtil.exe" $destination = Join-Path -Path $basePath -ChildPath "IntuneWinAppUtil.exe" try { From 2cc20c383f5eddc41c6a803ea4339e3cffd3fd74 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:09:10 -0400 Subject: [PATCH 067/107] Build-IntuneAppBuilder.ps1 --- Build-IntuneAppBuilder.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Build-IntuneAppBuilder.ps1 b/Build-IntuneAppBuilder.ps1 index 5423fa7..1fbdf3c 100644 --- a/Build-IntuneAppBuilder.ps1 +++ b/Build-IntuneAppBuilder.ps1 @@ -20,14 +20,18 @@ foreach ($folder in $folders) { } } -# Download IntuneWinAppUtil -$downloadUrl = "https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool/blob/master/IntuneWinAppUtil.exe" +# Updated correct binary download URL +$downloadUrl = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" $destination = Join-Path -Path $basePath -ChildPath "IntuneWinAppUtil.exe" try { Write-Host "Downloading IntuneWinAppUtil.exe..." Invoke-WebRequest -Uri $downloadUrl -OutFile $destination -UseBasicParsing Write-Host "Download complete: $destination" + + # Unblock the file + Unblock-File -Path $destination + Write-Host "File unblocked." } catch { Write-Warning "Failed to download IntuneWinAppUtil.exe: $_" } From 8ea897123fb1535bd917c337156bea0d22a6b64b Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 1 Aug 2025 09:45:11 -0400 Subject: [PATCH 068/107] Add files via upload --- Intune-AutoEnrollment-Usage.md | 59 ++++++++ Intune-AutoEnrollment.ps1 | 239 +++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 Intune-AutoEnrollment-Usage.md create mode 100644 Intune-AutoEnrollment.ps1 diff --git a/Intune-AutoEnrollment-Usage.md b/Intune-AutoEnrollment-Usage.md new file mode 100644 index 0000000..0a5cc4c --- /dev/null +++ b/Intune-AutoEnrollment-Usage.md @@ -0,0 +1,59 @@ +# Intune AutoEnrollment Script - Usage Instructions + +## Overview +This script provides automated Intune enrollment for Entra-joined devices with comprehensive error handling, logging, and diagnostic capabilities. + +## Usage Instructions + +To use this script effectively: + +- **1. Prerequisites Check** - Ensure all users have appropriate Intune licenses and enrollment permissions. +- **2. Configure Auto-Enrollment** - In Entra ID admin center, set up MDM User Scope and MDM URLs. +- **3. Deployment** - Deploy via Datto RMM or other management platform +- **4. Monitoring** - Track results with the standardized exit codes: + - 0 = Success (enrolled) + - 1 = Not Entra-joined + - 2 = DeviceEnroller missing + - 3 = Pending enrollment + - 4 = Deferred (no interactive user) + - 5 = Policy write failure + +## Modular Design + +The script is designed with modularity in mind, allowing for flexible implementation: + +- **Enhanced Device Information** - Uses CIM instead of WMI for better performance and compatibility. +- **Scheduled Task Discovery** - Robustly identifies and triggers the appropriate enrollment tasks. +- **Event Log Checking** - Comprehensive verification of enrollment status through Windows event logs. +- **Interactive User Detection** - Identifies if a user is logged in for PRT-dependent enrollment. +- **Architecture-Agnostic Registry Operations** - Properly handles registry operations regardless of OS architecture. +- **DeviceEnroller With Retry Logic** - Implements intelligent retry mechanisms for enrollment attempts. +- **MDM Diagnostics Collection** - Captures diagnostic information when enrollment fails for troubleshooting. +- **Network Awareness** - Identifies proxy configurations that might affect enrollment. + +Each of these components can be implemented independently or as part of the complete solution, allowing for customization based on specific organizational needs. + +## Prerequisites + +- Windows 10/11 device +- Device must be Entra ID (Azure AD) joined +- User must have appropriate Intune licenses +- MDM auto-enrollment configured in Entra ID + +## Logging + +The script creates detailed logs at: +- Path: `$env:ProgramData\IntuneEnrollment` +- Format: `EnrollmentLog_YYYYMMDD_HHMMSS.log` + +## Troubleshooting + +If enrollment fails, the script automatically: +- Captures MDM diagnostic information +- Logs detailed error information +- Provides specific exit codes for targeted remediation + +For manual troubleshooting, check: +1. Event Viewer: Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin +2. Registry: HKLM\SOFTWARE\Microsoft\Enrollments +3. Scheduled Tasks: \Microsoft\Windows\EnterpriseMgmt\ \ No newline at end of file diff --git a/Intune-AutoEnrollment.ps1 b/Intune-AutoEnrollment.ps1 new file mode 100644 index 0000000..f1f895b --- /dev/null +++ b/Intune-AutoEnrollment.ps1 @@ -0,0 +1,239 @@ +<# +.SYNOPSIS + Automated Intune enrollment script for Entra-joined devices +.DESCRIPTION + This script checks if a device is Entra-joined and triggers Intune enrollment. + It includes robust error handling, diagnostics, and logging features. +.NOTES + Version: 2.0 + Created: August 1, 2025 +#> + +# Script Configuration +$VerbosePreference = 'Continue' +$LogPath = "$env:ProgramData\IntuneEnrollment" +$LogFile = "$LogPath\EnrollmentLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + +# Ensure log directory exists +if (-not (Test-Path $LogPath)) { + New-Item -Path $LogPath -ItemType Directory -Force | Out-Null +} + +# Logging Function +function Write-Log { + param([string]$Message) + + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logEntry = "[$timestamp] $Message" + + Write-Verbose $logEntry + Add-Content -Path $LogFile -Value $logEntry +} + +# Enhanced Device Information Collection +function Get-DeviceDetails { + $computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem + $bios = Get-CimInstance -ClassName Win32_BIOS + + $props = @{ + ComputerName = $computerSystem.Name + Manufacturer = $computerSystem.Manufacturer + Model = $computerSystem.Model + SerialNumber = $bios.SerialNumber + EnrollmentStatus = "Unknown" + } + return New-Object -TypeName PSObject -Property $props +} + +# Interactive User Detection +function Get-LoggedOnUserUpn { + try { + $sid = (Get-Process -IncludeUserName -ErrorAction SilentlyContinue | + Where-Object { $_.SessionId -gt 0 } | Select-Object -First 1).UserName + if ($sid) { return $sid } + } catch {} + return $null +} + +# DeviceEnroller With Retry Logic +function Invoke-DeviceEnroller { + param([int]$attempt=1) + $enroller = "$env:WINDIR\System32\deviceenroller.exe" + if (Test-Path $enroller) { + Write-Log "Invoking DeviceEnroller (attempt $attempt)..." + $p = Start-Process -FilePath $enroller -ArgumentList "/c /AutoEnrollMDM" -PassThru -WindowStyle Hidden -Wait + return $p.ExitCode + } + return 2 # DeviceEnroller not found +} + +# Main Script Logic +Write-Log "Starting Intune enrollment script..." + +# Check if device is Entra-joined +$isEntraJoined = $false +$deviceDetails = Get-DeviceDetails + +try { + $dsregCmd = dsregcmd /status + $isEntraJoined = $dsregCmd -match "AzureAdJoined : YES" + + if ($isEntraJoined) { + Write-Log "Device is Entra-joined, proceeding with enrollment." + } else { + Write-Log "Device is not Entra-joined, cannot enroll in Intune." + exit 1 + } +} catch { + Write-Log "Error checking Entra join status: $_" + exit 1 +} + +# Check for proxy configuration +$netsh = (netsh winhttp show proxy) 2>$null +if ($netsh -and ($netsh -match 'Proxy Server')) { + Write-Log "WinHTTP proxy in use: $($netsh -replace '\s+',' ')" +} + +# Check if already enrolled in Intune +$enrolled = $false +$alreadyEnrolled = $false + +# Architecture-Agnostic Registry Operations +$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, + [Microsoft.Win32.RegistryView]::Registry64) +$enrollKey = $base.OpenSubKey("SOFTWARE\Microsoft\Enrollments") + +if ($enrollKey) { + foreach ($sub in $enrollKey.GetSubKeyNames()) { + if ($sub -match '^\{[0-9A-F-]+\}$') { + $prov = $enrollKey.OpenSubKey($sub).GetValue('ProviderID', $null) + if ($prov -eq 'MS DM Server') { + $alreadyEnrolled = $true + Write-Log "Device is already enrolled in Intune." + break + } + } + } +} + +if ($alreadyEnrolled) { + $enrolled = $true +} else { + # Check for interactive user + $userPresent = [bool](Get-LoggedOnUserUpn) + if (-not $userPresent) { + Write-Log "No interactive user detected; enrollment may defer until next sign-in." + } + + # Hardened MDM Policy Write + $mdmKey = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM" + try { + New-Item -Path $mdmKey -Force -ErrorAction SilentlyContinue | Out-Null + New-ItemProperty -Path $mdmKey -Name 'AutoEnrollMDM' -PropertyType DWord -Value 1 -Force | Out-Null + New-ItemProperty -Path $mdmKey -Name 'UseAADCredentialType' -PropertyType DWord -Value 1 -Force | Out-Null + + $ae = (Get-ItemProperty $mdmKey).AutoEnrollMDM + $ct = (Get-ItemProperty $mdmKey).UseAADCredentialType + + if ($ae -ne 1 -or $ct -ne 1) { + Write-Log "Policy write failed verification." + exit 5 + } + } catch { + Write-Log "Policy write failed: $_" + exit 5 + } + + # Improved Scheduled Task Discovery + $emPath = "\Microsoft\Windows\EnterpriseMgmt\" + $tasks = Get-ScheduledTask -TaskPath $emPath -ErrorAction SilentlyContinue | Where-Object { + $_.TaskName -match 'enrollment client|PushLaunch' + } + + if ($tasks) { + $toStart = $tasks | Sort-Object {[datetime]$_.LastRunTime} -Descending | Select-Object -First 1 + Write-Log "Starting enrollment task '$($toStart.TaskName)'." + Start-ScheduledTask -TaskPath $emPath -TaskName $toStart.TaskName + } else { + # Fall back to DeviceEnroller.exe + $enrollerExitCode = Invoke-DeviceEnroller -attempt 1 + if ($enrollerExitCode -ne 0) { + Write-Log "First DeviceEnroller attempt returned $enrollerExitCode, retrying after delay..." + Start-Sleep 10 + $enrollerExitCode = Invoke-DeviceEnroller -attempt 2 + } + } + + # Wait for enrollment to complete + Write-Log "Waiting for enrollment to process..." + Start-Sleep -Seconds 30 + + # Comprehensive Event Log Checking + try { + $dmLog = "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin" + $recentIds = 75, 76, 201, 202, 205, 207, 208, 209, 301, 305 + $events = Get-WinEvent -LogName $dmLog -MaxEvents 300 -ErrorAction SilentlyContinue | + Where-Object { $_.Id -in $recentIds -and $_.TimeCreated -gt (Get-Date).AddHours(-24) } + + if ($events -and $events.Count -gt 0) { + Write-Log "Found $($events.Count) recent MDM events indicating enrollment activity." + $enrolled = $true + } + } catch { + Write-Log "Error checking event logs: $_" + # Fallback with smaller query if the first one fails + try { + $events = Get-WinEvent -LogName $dmLog -Oldest -MaxEvents 50 -ErrorAction SilentlyContinue | + Where-Object { $_.Id -in $recentIds } + if ($events -and $events.Count -gt 0) { + Write-Log "Found $($events.Count) MDM events in fallback query." + $enrolled = $true + } + } catch { + Write-Log "Fallback event query also failed: $_" + } + } +} + +# MDM Diagnostics Collection on Failure +if (-not $enrolled) { + try { + $diagPath = "$env:ProgramData\IntuneDiagnostics" + if (-not (Test-Path $diagPath)) { New-Item -Path $diagPath -ItemType Directory -Force | Out-Null } + + $cab = Join-Path $diagPath ("MDMDiag_{0}.cab" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) + Start-Process -FilePath "$env:SystemRoot\System32\mdmdiagnosticstool.exe" ` + -ArgumentList "-area DeviceEnrollment;DeviceProvisioning;Autopilot -cab $cab" -Wait -WindowStyle Hidden + Write-Log "Captured MDMDiagnostics to $cab" + } catch { + Write-Log "MDMDiagnostics failed: $_" + } +} + +# Improved Exit Code Handling +if ($enrolled) { + $deviceDetails.EnrollmentStatus = "Enrolled" + Write-Log "Device is successfully enrolled in Intune." + $exitCode = 0 # Success +} elseif (-not $isEntraJoined) { + $deviceDetails.EnrollmentStatus = "Not Entra Joined" + Write-Log "Device is not Entra joined, cannot enroll." + $exitCode = 1 # Not Entra joined +} elseif (-not (Test-Path "$env:WINDIR\System32\deviceenroller.exe")) { + $deviceDetails.EnrollmentStatus = "Missing DeviceEnroller" + Write-Log "DeviceEnroller.exe not found." + $exitCode = 2 # DeviceEnroller missing +} elseif (-not $userPresent) { + $deviceDetails.EnrollmentStatus = "Deferred - No User" + Write-Log "Enrollment deferred until user login." + $exitCode = 4 # No interactive user +} else { + $deviceDetails.EnrollmentStatus = "Pending" + Write-Log "Enrollment attempted but not yet complete." + $exitCode = 3 # Pending +} + +# Surface friendly result to Datto output +Write-Output "DATTO:EnrollmentStatus=$($deviceDetails.EnrollmentStatus);Computer=$($deviceDetails.ComputerName);Serial=$($deviceDetails.SerialNumber)" +exit $exitCode \ No newline at end of file From 8b40f1d9926b1e6bce1f0a81a1cac894e93cd862 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:23:49 -0400 Subject: [PATCH 069/107] Detect-WUEntries.ps1 --- Detect-WUEntries.ps1 | 150 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Detect-WUEntries.ps1 diff --git a/Detect-WUEntries.ps1 b/Detect-WUEntries.ps1 new file mode 100644 index 0000000..4cbdb4b --- /dev/null +++ b/Detect-WUEntries.ps1 @@ -0,0 +1,150 @@ +# Remediation Script for Windows Update Policy Conflicts +# Removes conflicting GPO/WSUS registry entries to allow Intune Windows Update management + +try { + $RemediationApplied = $false + $RemovedEntries = @() + + # Define registry paths to clean + $RegistryPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + ) + + # Define problematic registry values to remove + $ProblematicValues = @( + "WUServer", + "WUStatusServer", + "TargetGroup", + "TargetGroupEnabled", + "AcceptTrustedPublisherCerts", + "ElevateNonAdmins", + "AUOptions", + "NoAutoUpdate", + "DisableDualScan" + ) + + Write-Output "Starting Windows Update policy conflict remediation..." + + # Stop Windows Update service before making changes + Write-Output "Stopping Windows Update service..." + Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue + + # Clean main registry paths + foreach ($Path in $RegistryPaths) { + if (Test-Path $Path) { + Write-Output "Processing registry path: $Path" + + foreach ($Value in $ProblematicValues) { + try { + $RegProperty = Get-ItemProperty -Path $Path -Name $Value -ErrorAction SilentlyContinue + if ($RegProperty) { + Write-Output "Removing conflicting value: $Path\$Value" + Remove-ItemProperty -Path $Path -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$Path\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $Path\$Value : $($_.Exception.Message)" + } + } + } + } + + # Clean AU (Automatic Updates) specific entries + $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + if (Test-Path $AUPath) { + Write-Output "Processing AU registry path: $AUPath" + + $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") + foreach ($Value in $AUValues) { + try { + $RegProperty = Get-ItemProperty -Path $AUPath -Name $Value -ErrorAction SilentlyContinue + if ($RegProperty) { + Write-Output "Removing AU conflicting value: $AUPath\$Value" + Remove-ItemProperty -Path $AUPath -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$AUPath\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $AUPath\$Value : $($_.Exception.Message)" + } + } + } + + # Remove empty registry keys if they exist and are empty + $PathsToCleanup = @( + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + ) + + foreach ($CleanupPath in $PathsToCleanup) { + if (Test-Path $CleanupPath) { + $SubKeys = Get-ChildItem -Path $CleanupPath -ErrorAction SilentlyContinue + $Properties = Get-ItemProperty -Path $CleanupPath -ErrorAction SilentlyContinue + + # If no subkeys and no properties (except default PS properties), remove the key + if ((-not $SubKeys) -and ($Properties.PSObject.Properties.Name.Count -le 4)) { + try { + Write-Output "Removing empty registry key: $CleanupPath" + Remove-Item -Path $CleanupPath -Force -ErrorAction Stop + $RemovedEntries += "$CleanupPath (empty key)" + $RemediationApplied = $true + } + catch { + Write-Warning "Failed to remove empty key $CleanupPath : $($_.Exception.Message)" + } + } + } + } + + # Force Group Policy refresh to clear cached policies + if ($RemediationApplied) { + Write-Output "Forcing Group Policy refresh..." + & gpupdate /force /wait:0 2>&1 | Out-String | Write-Output + + # Restart Windows Update service + Write-Output "Restarting Windows Update service..." + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + + # Reset Windows Update components + Write-Output "Resetting Windows Update components..." + & net stop wuauserv 2>&1 | Out-Null + & net stop cryptSvc 2>&1 | Out-Null + & net stop bits 2>&1 | Out-Null + & net stop msiserver 2>&1 | Out-Null + + Start-Sleep -Seconds 2 + + & net start wuauserv 2>&1 | Out-Null + & net start cryptSvc 2>&1 | Out-Null + & net start bits 2>&1 | Out-Null + & net start msiserver 2>&1 | Out-Null + } + else { + # Start the service back up even if no changes were made + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + } + + # Log results + if ($RemediationApplied) { + Write-Output "REMEDIATION SUCCESSFUL: Removed $($RemovedEntries.Count) conflicting entries:" + $RemovedEntries | ForEach-Object { Write-Output " - $_" } + Write-Output "Windows Update policy conflicts have been resolved. Intune can now manage updates." + Write-Output "A device restart may be required for all changes to take effect." + exit 0 + } + else { + Write-Output "REMEDIATION RESULT: No conflicting entries found to remove." + exit 0 + } +} +catch { + # Ensure Windows Update service is running even if script fails + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + + Write-Error "Remediation script failed: $($_.Exception.Message)" + exit 1 +} From ff43a635317a73c6cf59f29d6c59535c3f49b610 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:24:38 -0400 Subject: [PATCH 070/107] Remediate-WUEntries.ps1 --- Remediate-WUEntries.ps1 | 150 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Remediate-WUEntries.ps1 diff --git a/Remediate-WUEntries.ps1 b/Remediate-WUEntries.ps1 new file mode 100644 index 0000000..4cbdb4b --- /dev/null +++ b/Remediate-WUEntries.ps1 @@ -0,0 +1,150 @@ +# Remediation Script for Windows Update Policy Conflicts +# Removes conflicting GPO/WSUS registry entries to allow Intune Windows Update management + +try { + $RemediationApplied = $false + $RemovedEntries = @() + + # Define registry paths to clean + $RegistryPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + ) + + # Define problematic registry values to remove + $ProblematicValues = @( + "WUServer", + "WUStatusServer", + "TargetGroup", + "TargetGroupEnabled", + "AcceptTrustedPublisherCerts", + "ElevateNonAdmins", + "AUOptions", + "NoAutoUpdate", + "DisableDualScan" + ) + + Write-Output "Starting Windows Update policy conflict remediation..." + + # Stop Windows Update service before making changes + Write-Output "Stopping Windows Update service..." + Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue + + # Clean main registry paths + foreach ($Path in $RegistryPaths) { + if (Test-Path $Path) { + Write-Output "Processing registry path: $Path" + + foreach ($Value in $ProblematicValues) { + try { + $RegProperty = Get-ItemProperty -Path $Path -Name $Value -ErrorAction SilentlyContinue + if ($RegProperty) { + Write-Output "Removing conflicting value: $Path\$Value" + Remove-ItemProperty -Path $Path -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$Path\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $Path\$Value : $($_.Exception.Message)" + } + } + } + } + + # Clean AU (Automatic Updates) specific entries + $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + if (Test-Path $AUPath) { + Write-Output "Processing AU registry path: $AUPath" + + $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") + foreach ($Value in $AUValues) { + try { + $RegProperty = Get-ItemProperty -Path $AUPath -Name $Value -ErrorAction SilentlyContinue + if ($RegProperty) { + Write-Output "Removing AU conflicting value: $AUPath\$Value" + Remove-ItemProperty -Path $AUPath -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$AUPath\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $AUPath\$Value : $($_.Exception.Message)" + } + } + } + + # Remove empty registry keys if they exist and are empty + $PathsToCleanup = @( + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + ) + + foreach ($CleanupPath in $PathsToCleanup) { + if (Test-Path $CleanupPath) { + $SubKeys = Get-ChildItem -Path $CleanupPath -ErrorAction SilentlyContinue + $Properties = Get-ItemProperty -Path $CleanupPath -ErrorAction SilentlyContinue + + # If no subkeys and no properties (except default PS properties), remove the key + if ((-not $SubKeys) -and ($Properties.PSObject.Properties.Name.Count -le 4)) { + try { + Write-Output "Removing empty registry key: $CleanupPath" + Remove-Item -Path $CleanupPath -Force -ErrorAction Stop + $RemovedEntries += "$CleanupPath (empty key)" + $RemediationApplied = $true + } + catch { + Write-Warning "Failed to remove empty key $CleanupPath : $($_.Exception.Message)" + } + } + } + } + + # Force Group Policy refresh to clear cached policies + if ($RemediationApplied) { + Write-Output "Forcing Group Policy refresh..." + & gpupdate /force /wait:0 2>&1 | Out-String | Write-Output + + # Restart Windows Update service + Write-Output "Restarting Windows Update service..." + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + + # Reset Windows Update components + Write-Output "Resetting Windows Update components..." + & net stop wuauserv 2>&1 | Out-Null + & net stop cryptSvc 2>&1 | Out-Null + & net stop bits 2>&1 | Out-Null + & net stop msiserver 2>&1 | Out-Null + + Start-Sleep -Seconds 2 + + & net start wuauserv 2>&1 | Out-Null + & net start cryptSvc 2>&1 | Out-Null + & net start bits 2>&1 | Out-Null + & net start msiserver 2>&1 | Out-Null + } + else { + # Start the service back up even if no changes were made + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + } + + # Log results + if ($RemediationApplied) { + Write-Output "REMEDIATION SUCCESSFUL: Removed $($RemovedEntries.Count) conflicting entries:" + $RemovedEntries | ForEach-Object { Write-Output " - $_" } + Write-Output "Windows Update policy conflicts have been resolved. Intune can now manage updates." + Write-Output "A device restart may be required for all changes to take effect." + exit 0 + } + else { + Write-Output "REMEDIATION RESULT: No conflicting entries found to remove." + exit 0 + } +} +catch { + # Ensure Windows Update service is running even if script fails + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + + Write-Error "Remediation script failed: $($_.Exception.Message)" + exit 1 +} From 797953f94bdeaded79185a855fff3ae31be52162 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:25:05 -0400 Subject: [PATCH 071/107] Detect-WUEntries.ps1 --- Detect-WUEntries.ps1 | 141 +++++++++++-------------------------------- 1 file changed, 35 insertions(+), 106 deletions(-) diff --git a/Detect-WUEntries.ps1 b/Detect-WUEntries.ps1 index 4cbdb4b..4812b38 100644 --- a/Detect-WUEntries.ps1 +++ b/Detect-WUEntries.ps1 @@ -1,17 +1,17 @@ -# Remediation Script for Windows Update Policy Conflicts -# Removes conflicting GPO/WSUS registry entries to allow Intune Windows Update management +# Detection Script for Windows Update Policy Conflicts +# Checks for conflicting GPO/WSUS registry entries that prevent Intune Windows Update management try { - $RemediationApplied = $false - $RemovedEntries = @() + $ConflictFound = $false + $ConflictingEntries = @() - # Define registry paths to clean + # Define registry paths to check $RegistryPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate", "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" ) - # Define problematic registry values to remove + # Define problematic registry values that indicate GPO/WSUS control $ProblematicValues = @( "WUServer", "WUStatusServer", @@ -24,127 +24,56 @@ try { "DisableDualScan" ) - Write-Output "Starting Windows Update policy conflict remediation..." - - # Stop Windows Update service before making changes - Write-Output "Stopping Windows Update service..." - Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue - - # Clean main registry paths + # Check each registry path foreach ($Path in $RegistryPaths) { if (Test-Path $Path) { - Write-Output "Processing registry path: $Path" + Write-Output "Checking registry path: $Path" + + # Get all properties in the registry key + $RegKey = Get-ItemProperty -Path $Path -ErrorAction SilentlyContinue - foreach ($Value in $ProblematicValues) { - try { - $RegProperty = Get-ItemProperty -Path $Path -Name $Value -ErrorAction SilentlyContinue - if ($RegProperty) { - Write-Output "Removing conflicting value: $Path\$Value" - Remove-ItemProperty -Path $Path -Name $Value -Force -ErrorAction Stop - $RemovedEntries += "$Path\$Value" - $RemediationApplied = $true + if ($RegKey) { + # Check for problematic values + foreach ($Value in $ProblematicValues) { + if ($RegKey.PSObject.Properties.Name -contains $Value) { + $ConflictFound = $true + $ConflictingEntries += "$Path\$Value" + Write-Output "CONFLICT FOUND: $Path\$Value = $($RegKey.$Value)" } } - catch { - Write-Warning "Failed to remove $Path\$Value : $($_.Exception.Message)" - } } } } - # Clean AU (Automatic Updates) specific entries + # Additional check for AU (Automatic Updates) registry entries $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" if (Test-Path $AUPath) { - Write-Output "Processing AU registry path: $AUPath" - - $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") - foreach ($Value in $AUValues) { - try { - $RegProperty = Get-ItemProperty -Path $AUPath -Name $Value -ErrorAction SilentlyContinue - if ($RegProperty) { - Write-Output "Removing AU conflicting value: $AUPath\$Value" - Remove-ItemProperty -Path $AUPath -Name $Value -Force -ErrorAction Stop - $RemovedEntries += "$AUPath\$Value" - $RemediationApplied = $true + $AURegKey = Get-ItemProperty -Path $AUPath -ErrorAction SilentlyContinue + if ($AURegKey) { + $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") + foreach ($Value in $AUValues) { + if ($AURegKey.PSObject.Properties.Name -contains $Value) { + $ConflictFound = $true + $ConflictingEntries += "$AUPath\$Value" + Write-Output "CONFLICT FOUND: $AUPath\$Value = $($AURegKey.$Value)" } } - catch { - Write-Warning "Failed to remove $AUPath\$Value : $($_.Exception.Message)" - } } } - # Remove empty registry keys if they exist and are empty - $PathsToCleanup = @( - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", - "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" - ) - - foreach ($CleanupPath in $PathsToCleanup) { - if (Test-Path $CleanupPath) { - $SubKeys = Get-ChildItem -Path $CleanupPath -ErrorAction SilentlyContinue - $Properties = Get-ItemProperty -Path $CleanupPath -ErrorAction SilentlyContinue - - # If no subkeys and no properties (except default PS properties), remove the key - if ((-not $SubKeys) -and ($Properties.PSObject.Properties.Name.Count -le 4)) { - try { - Write-Output "Removing empty registry key: $CleanupPath" - Remove-Item -Path $CleanupPath -Force -ErrorAction Stop - $RemovedEntries += "$CleanupPath (empty key)" - $RemediationApplied = $true - } - catch { - Write-Warning "Failed to remove empty key $CleanupPath : $($_.Exception.Message)" - } - } - } - } - - # Force Group Policy refresh to clear cached policies - if ($RemediationApplied) { - Write-Output "Forcing Group Policy refresh..." - & gpupdate /force /wait:0 2>&1 | Out-String | Write-Output - - # Restart Windows Update service - Write-Output "Restarting Windows Update service..." - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue - - # Reset Windows Update components - Write-Output "Resetting Windows Update components..." - & net stop wuauserv 2>&1 | Out-Null - & net stop cryptSvc 2>&1 | Out-Null - & net stop bits 2>&1 | Out-Null - & net stop msiserver 2>&1 | Out-Null - - Start-Sleep -Seconds 2 - - & net start wuauserv 2>&1 | Out-Null - & net start cryptSvc 2>&1 | Out-Null - & net start bits 2>&1 | Out-Null - & net start msiserver 2>&1 | Out-Null - } - else { - # Start the service back up even if no changes were made - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue - } - # Log results - if ($RemediationApplied) { - Write-Output "REMEDIATION SUCCESSFUL: Removed $($RemovedEntries.Count) conflicting entries:" - $RemovedEntries | ForEach-Object { Write-Output " - $_" } - Write-Output "Windows Update policy conflicts have been resolved. Intune can now manage updates." - Write-Output "A device restart may be required for all changes to take effect." - exit 0 + if ($ConflictFound) { + Write-Output "DETECTION RESULT: Conflicts detected. Found $($ConflictingEntries.Count) conflicting entries:" + $ConflictingEntries | ForEach-Object { Write-Output " - $_" } + Write-Output "Remediation required to allow Intune Windows Update management." + exit 1 # Exit with error code to trigger remediation } else { - Write-Output "REMEDIATION RESULT: No conflicting entries found to remove." - exit 0 + Write-Output "DETECTION RESULT: No Windows Update policy conflicts found. Intune can manage updates properly." + exit 0 # Exit success - no remediation needed } } catch { - # Ensure Windows Update service is running even if script fails - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue - - Write-Error "Remediation script failed: $($_.Exception.Message)" + Write-Error "Detection script failed: $($_.Exception.Message)" exit 1 } From 3e92f3346b8359af41fdef2365ba0e1cbccd4805 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:57:53 -0400 Subject: [PATCH 072/107] Remediate-WUEntries.ps1 --- Remediate-WUEntries.ps1 | 211 ++++++++++++++++++++++++++-------------- 1 file changed, 137 insertions(+), 74 deletions(-) diff --git a/Remediate-WUEntries.ps1 b/Remediate-WUEntries.ps1 index 4cbdb4b..943588e 100644 --- a/Remediate-WUEntries.ps1 +++ b/Remediate-WUEntries.ps1 @@ -1,10 +1,20 @@ -# Remediation Script for Windows Update Policy Conflicts +# Optimized Remediation Script for Windows Update Policy Conflicts # Removes conflicting GPO/WSUS registry entries to allow Intune Windows Update management +# Optimized for speed to avoid Proactive Remediation timeouts + +#Requires -RunAsAdministrator try { + # Set execution timeout tracking + $ScriptStartTime = Get-Date + $MaxExecutionMinutes = 25 # Leave 5 minutes buffer before 30-min timeout + $RemediationApplied = $false $RemovedEntries = @() + Write-Output "Starting optimized Windows Update policy conflict remediation..." + Write-Output "Script start time: $ScriptStartTime" + # Define registry paths to clean $RegistryPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate", @@ -24,74 +34,109 @@ try { "DisableDualScan" ) - Write-Output "Starting Windows Update policy conflict remediation..." + # Quick function to check if we're approaching timeout + function Test-TimeoutApproaching { + $ElapsedMinutes = ((Get-Date) - $ScriptStartTime).TotalMinutes + return $ElapsedMinutes -gt $MaxExecutionMinutes + } - # Stop Windows Update service before making changes - Write-Output "Stopping Windows Update service..." - Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue + # Optimized service stop - don't wait for full stop + Write-Output "Stopping Windows Update service (non-blocking)..." + try { + Stop-Service -Name "wuauserv" -Force -NoWait -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 # Brief pause instead of waiting for full stop + } + catch { + Write-Warning "Service stop warning: $($_.Exception.Message)" + } - # Clean main registry paths + # Clean main registry paths - optimized loop foreach ($Path in $RegistryPaths) { + if (Test-TimeoutApproaching) { + Write-Warning "Approaching timeout, stopping registry cleanup early" + break + } + if (Test-Path $Path) { Write-Output "Processing registry path: $Path" - foreach ($Value in $ProblematicValues) { - try { - $RegProperty = Get-ItemProperty -Path $Path -Name $Value -ErrorAction SilentlyContinue - if ($RegProperty) { - Write-Output "Removing conflicting value: $Path\$Value" - Remove-ItemProperty -Path $Path -Name $Value -Force -ErrorAction Stop - $RemovedEntries += "$Path\$Value" - $RemediationApplied = $true + # Get all properties at once for efficiency + try { + $RegItem = Get-Item -Path $Path -ErrorAction SilentlyContinue + if ($RegItem) { + foreach ($Value in $ProblematicValues) { + try { + if ($RegItem.GetValue($Value, $null) -ne $null) { + Write-Output "Removing: $Path\$Value" + Remove-ItemProperty -Path $Path -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$Path\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $Path\$Value : $($_.Exception.Message)" + } } } - catch { - Write-Warning "Failed to remove $Path\$Value : $($_.Exception.Message)" - } + } + catch { + Write-Warning "Failed to access $Path : $($_.Exception.Message)" } } } - # Clean AU (Automatic Updates) specific entries - $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" - if (Test-Path $AUPath) { - Write-Output "Processing AU registry path: $AUPath" - - $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") - foreach ($Value in $AUValues) { + # Clean AU (Automatic Updates) specific entries - optimized + if (-not (Test-TimeoutApproaching)) { + $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + if (Test-Path $AUPath) { + Write-Output "Processing AU registry path: $AUPath" + + $AUValues = @("UseWUServer", "NoAutoUpdate", "AUOptions", "ScheduledInstallDay", "ScheduledInstallTime") + try { - $RegProperty = Get-ItemProperty -Path $AUPath -Name $Value -ErrorAction SilentlyContinue - if ($RegProperty) { - Write-Output "Removing AU conflicting value: $AUPath\$Value" - Remove-ItemProperty -Path $AUPath -Name $Value -Force -ErrorAction Stop - $RemovedEntries += "$AUPath\$Value" - $RemediationApplied = $true + $AUItem = Get-Item -Path $AUPath -ErrorAction SilentlyContinue + if ($AUItem) { + foreach ($Value in $AUValues) { + try { + if ($AUItem.GetValue($Value, $null) -ne $null) { + Write-Output "Removing AU value: $AUPath\$Value" + Remove-ItemProperty -Path $AUPath -Name $Value -Force -ErrorAction Stop + $RemovedEntries += "$AUPath\$Value" + $RemediationApplied = $true + } + } + catch { + Write-Warning "Failed to remove $AUPath\$Value : $($_.Exception.Message)" + } + } } } catch { - Write-Warning "Failed to remove $AUPath\$Value : $($_.Exception.Message)" + Write-Warning "Failed to access AU path: $($_.Exception.Message)" } } } - # Remove empty registry keys if they exist and are empty - $PathsToCleanup = @( - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", - "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" - ) - - foreach ($CleanupPath in $PathsToCleanup) { - if (Test-Path $CleanupPath) { - $SubKeys = Get-ChildItem -Path $CleanupPath -ErrorAction SilentlyContinue - $Properties = Get-ItemProperty -Path $CleanupPath -ErrorAction SilentlyContinue - - # If no subkeys and no properties (except default PS properties), remove the key - if ((-not $SubKeys) -and ($Properties.PSObject.Properties.Name.Count -le 4)) { + # Remove empty registry keys if they exist and are empty - quick check only + if (-not (Test-TimeoutApproaching)) { + $PathsToCleanup = @( + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + ) + + foreach ($CleanupPath in $PathsToCleanup) { + if (Test-Path $CleanupPath) { try { - Write-Output "Removing empty registry key: $CleanupPath" - Remove-Item -Path $CleanupPath -Force -ErrorAction Stop - $RemovedEntries += "$CleanupPath (empty key)" - $RemediationApplied = $true + $SubKeys = @(Get-ChildItem -Path $CleanupPath -ErrorAction SilentlyContinue) + $Properties = Get-ItemProperty -Path $CleanupPath -ErrorAction SilentlyContinue + + # Quick check - if no subkeys and minimal properties, remove + if ($SubKeys.Count -eq 0 -and $Properties.PSObject.Properties.Name.Count -le 4) { + Write-Output "Removing empty registry key: $CleanupPath" + Remove-Item -Path $CleanupPath -Force -ErrorAction Stop + $RemovedEntries += "$CleanupPath (empty key)" + $RemediationApplied = $true + } } catch { Write-Warning "Failed to remove empty key $CleanupPath : $($_.Exception.Message)" @@ -100,40 +145,49 @@ try { } } - # Force Group Policy refresh to clear cached policies + # OPTIMIZED: Skip gpupdate and complex service restarts if approaching timeout if ($RemediationApplied) { - Write-Output "Forcing Group Policy refresh..." - & gpupdate /force /wait:0 2>&1 | Out-String | Write-Output - - # Restart Windows Update service - Write-Output "Restarting Windows Update service..." - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue - - # Reset Windows Update components - Write-Output "Resetting Windows Update components..." - & net stop wuauserv 2>&1 | Out-Null - & net stop cryptSvc 2>&1 | Out-Null - & net stop bits 2>&1 | Out-Null - & net stop msiserver 2>&1 | Out-Null - - Start-Sleep -Seconds 2 - - & net start wuauserv 2>&1 | Out-Null - & net start cryptSvc 2>&1 | Out-Null - & net start bits 2>&1 | Out-Null - & net start msiserver 2>&1 | Out-Null + if (Test-TimeoutApproaching) { + Write-Output "Timeout approaching - skipping GP refresh and service restarts" + Write-Output "Registry changes applied. Device restart recommended for full effect." + } + else { + # LIGHTWEIGHT service restart only + Write-Output "Performing lightweight service restart..." + try { + # Just restart Windows Update service, skip others to save time + Restart-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + Write-Output "Windows Update service restarted" + } + catch { + Write-Warning "Service restart had issues: $($_.Exception.Message)" + } + + # REMOVED: gpupdate /force (major timeout cause) + # REMOVED: Complex service stop/start sequence + Write-Output "Note: Group Policy refresh skipped to avoid timeout. Changes will apply on next GP refresh or reboot." + } } else { - # Start the service back up even if no changes were made - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + # Always ensure service is running + try { + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + } + catch { + Write-Warning "Could not start Windows Update service: $($_.Exception.Message)" + } } + # Calculate execution time + $ExecutionTime = ((Get-Date) - $ScriptStartTime).TotalMinutes + Write-Output "Script execution time: $([math]::Round($ExecutionTime, 2)) minutes" + # Log results if ($RemediationApplied) { Write-Output "REMEDIATION SUCCESSFUL: Removed $($RemovedEntries.Count) conflicting entries:" $RemovedEntries | ForEach-Object { Write-Output " - $_" } - Write-Output "Windows Update policy conflicts have been resolved. Intune can now manage updates." - Write-Output "A device restart may be required for all changes to take effect." + Write-Output "Windows Update policy conflicts have been resolved." + Write-Output "Intune can now manage updates. A device restart is recommended." exit 0 } else { @@ -142,8 +196,17 @@ try { } } catch { + # Calculate execution time even on error + $ExecutionTime = ((Get-Date) - $ScriptStartTime).TotalMinutes + Write-Output "Script execution time at error: $([math]::Round($ExecutionTime, 2)) minutes" + # Ensure Windows Update service is running even if script fails - Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + try { + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + } + catch { + Write-Warning "Could not start Windows Update service after error" + } Write-Error "Remediation script failed: $($_.Exception.Message)" exit 1 From 60c139ebfcef613a7bb6ca0dfcf245f6e9c63f84 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:34:38 -0400 Subject: [PATCH 073/107] Add files via upload Intune WSUS Cleanup --- Detect-IntuneUpdateSettings.ps1 | 75 +++++++++++++ Remediate-IntuneUpdateSettings.ps1 | 165 +++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 Detect-IntuneUpdateSettings.ps1 create mode 100644 Remediate-IntuneUpdateSettings.ps1 diff --git a/Detect-IntuneUpdateSettings.ps1 b/Detect-IntuneUpdateSettings.ps1 new file mode 100644 index 0000000..ad6164a --- /dev/null +++ b/Detect-IntuneUpdateSettings.ps1 @@ -0,0 +1,75 @@ +<# + DETECTION SCRIPT: Intune WSUS Policy Cleanup + Purpose: Detect any WSUS/Windows Update Group Policy settings that would conflict with Intune management + Logic: If ANY WSUS-related policy registry values exist -> Non-compliant (needs cleanup) + Exit 0 = compliant (Intune can manage), 1 = non-compliant (WSUS policies present) +#> + +$ErrorActionPreference = 'Stop' + +# Registry locations where Group Policy WSUS settings are stored +$wsusRegistryPaths = @( + @{ + Path = 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate' + Values = @('WUServer', 'WUStatusServer', 'DoNotConnectToWindowsUpdateInternetLocations', 'ElevateNonAdmins', 'TargetGroup', 'TargetGroupEnabled') + }, + @{ + Path = 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' + Values = @('UseWUServer', 'AUOptions', 'AutoInstallMinorUpdates', 'DetectionFrequency', 'DetectionFrequencyEnabled', 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', 'RebootRelaunchTimeout', 'RebootWarningTimeout', 'RescheduleWaitTime', 'ScheduledInstallDay', 'ScheduledInstallTime') + } +) + +# Function to check for policy values +function Get-WSUSPolicyValues { + $foundPolicies = @() + + foreach ($location in $wsusRegistryPaths) { + if (Test-Path $location.Path) { + try { + $regProps = Get-ItemProperty -Path $location.Path -ErrorAction SilentlyContinue + + foreach ($valueName in $location.Values) { + if ($null -ne $regProps.$valueName) { + $foundPolicies += [PSCustomObject]@{ + Path = $location.Path + Name = $valueName + Value = $regProps.$valueName + } + } + } + } + catch { + Write-Output "INFO: Could not read registry path $($location.Path): $($_.Exception.Message)" + } + } + } + + return $foundPolicies +} + +# Check for Windows Update policy values +$detectedPolicies = Get-WSUSPolicyValues + +if ($detectedPolicies.Count -eq 0) { + Write-Output "COMPLIANT: No WSUS/Windows Update Group Policy settings found. Device ready for Intune management." + exit 0 +} + +# If we found policies, device is non-compliant +$policyList = $detectedPolicies | ForEach-Object { "$($_.Path)\$($_.Name) = $($_.Value)" } +$policyString = $policyList -join '; ' + +Write-Output "NON-COMPLIANT: Found $($detectedPolicies.Count) WSUS/Windows Update policy settings that will interfere with Intune management. Policies: $policyString" + +# Additional check: Verify if device is Azure AD joined (good indicator it should use Intune) +try { + $deviceInfo = Get-CimInstance -Class Win32_ComputerSystem + if ($deviceInfo.PartOfDomain -and $deviceInfo.Domain -notlike "*.local") { + Write-Output "INFO: Device appears to be Azure AD joined ($($deviceInfo.Domain)) - should use Intune for updates." + } +} +catch { + # Don't fail detection if we can't get this info +} + +exit 1 \ No newline at end of file diff --git a/Remediate-IntuneUpdateSettings.ps1 b/Remediate-IntuneUpdateSettings.ps1 new file mode 100644 index 0000000..723a46e --- /dev/null +++ b/Remediate-IntuneUpdateSettings.ps1 @@ -0,0 +1,165 @@ +<# + REMEDIATION SCRIPT: Intune WSUS Policy Cleanup + Purpose: Remove ALL WSUS/Windows Update Group Policy settings to allow Intune management + This script forcibly removes WSUS policies regardless of their source (GPO/Local Policy) +#> + +$ErrorActionPreference = 'Stop' + +# Registry paths to clean up +$cleanupPaths = @( + 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate' +) + +# Function to safely remove registry keys with logging +function Remove-WSUSRegistry { + param ( + [string]$RegistryPath + ) + + if (-not (Test-Path $RegistryPath)) { + Write-Output "Path not present: $RegistryPath" + return $true + } + + try { + # Log what we're about to remove + $itemsToRemove = @() + Get-ChildItem -Path $RegistryPath -Recurse -ErrorAction SilentlyContinue | ForEach-Object { + $itemsToRemove += $_.PSPath + } + + # Get the properties in the root key + $rootProperties = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue + $propertyNames = ($rootProperties.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" }).Name + + if ($propertyNames) { + Write-Output "Removing properties from ${RegistryPath}: $($propertyNames -join ', ')" + } + + if ($itemsToRemove) { + Write-Output "Removing subkeys: $($itemsToRemove -join '; ')" + } + + # Remove the entire key and all subkeys + Remove-Item -Path $RegistryPath -Recurse -Force -ErrorAction Stop + Write-Output "Successfully removed: $RegistryPath" + return $true + } + catch { + Write-Output "ERROR: Failed to remove $RegistryPath - $($_.Exception.Message)" + return $false + } +} + +# Function to reset Windows Update components +function Reset-WindowsUpdateComponents { + try { + Write-Output "Resetting Windows Update components..." + + # Stop Windows Update services + $services = @('wuauserv', 'cryptsvc', 'bits', 'msiserver') + foreach ($service in $services) { + try { + Stop-Service -Name $service -Force -ErrorAction SilentlyContinue + Write-Output "Stopped service: $service" + } + catch { + Write-Output "Could not stop service $service (may not be running): $($_.Exception.Message)" + } + } + + # Clear Windows Update cache + $cacheLocations = @( + "$env:SystemRoot\SoftwareDistribution\Download", + "$env:SystemRoot\System32\catroot2" + ) + + foreach ($location in $cacheLocations) { + if (Test-Path $location) { + try { + Remove-Item -Path "$location\*" -Recurse -Force -ErrorAction SilentlyContinue + Write-Output "Cleared cache: $location" + } + catch { + Write-Output "Could not clear cache $location (files may be in use) - $($_.Exception.Message)" + } + } + } + + # Restart Windows Update services + foreach ($service in $services) { + try { + Start-Service -Name $service -ErrorAction SilentlyContinue + Write-Output "Started service: $service" + } + catch { + Write-Output "Could not start service $service - $($_.Exception.Message)" + } + } + + return $true + } + catch { + Write-Output "WARNING: Error during Windows Update component reset - $($_.Exception.Message)" + return $false + } +} + +# Main remediation logic +Write-Output "Starting WSUS policy cleanup for Intune management transition..." + +$allSuccess = $true + +# Remove WSUS registry policies +foreach ($path in $cleanupPaths) { + $result = Remove-WSUSRegistry -RegistryPath $path + if (-not $result) { + $allSuccess = $false + } +} + +# Reset Windows Update components to ensure clean state +$resetResult = Reset-WindowsUpdateComponents + +# Verify cleanup was successful +Write-Output "`nVerifying cleanup..." +$verificationPaths = @( + 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate', + 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' +) + +$remainingPolicies = @() +foreach ($path in $verificationPaths) { + if (Test-Path $path) { + $props = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue + $nonPSProps = ($props.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" }).Name + if ($nonPSProps) { + $remainingPolicies += "$path : $($nonPSProps -join ', ')" + } + } +} + +if ($remainingPolicies.Count -eq 0) { + Write-Output "SUCCESS: All WSUS policies removed. Device is now ready for Intune Windows Update management." + Write-Output "INFO: It may take up to 24 hours for Intune policies to fully take effect." +} else { + Write-Output "WARNING: Some policies may still remain: $($remainingPolicies -join '; ')" + $allSuccess = $false +} + +# Force a Group Policy refresh to clear any cached policy +try { + Write-Output "Refreshing Group Policy..." + & gpupdate /force /wait:0 +} catch { + Write-Output "Could not refresh Group Policy - $($_.Exception.Message)" +} + +if ($allSuccess) { + Write-Output "Remediation completed successfully." + exit 0 +} else { + Write-Output "Remediation completed with warnings. Manual verification may be required." + exit 1 +} \ No newline at end of file From 56195454bf3b5ed1f5ea58c41ff63097205271ef Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:46:03 -0400 Subject: [PATCH 074/107] Remediate-IntuneUpdateSettings.ps1 --- Remediate-IntuneUpdateSettings.ps1 | 68 +++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/Remediate-IntuneUpdateSettings.ps1 b/Remediate-IntuneUpdateSettings.ps1 index 723a46e..668ed4e 100644 --- a/Remediate-IntuneUpdateSettings.ps1 +++ b/Remediate-IntuneUpdateSettings.ps1 @@ -6,12 +6,13 @@ $ErrorActionPreference = 'Stop' -# Registry paths to clean up +# Registry paths to clean up - process both parent and child paths $cleanupPaths = @( + 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU', 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate' ) -# Function to safely remove registry keys with logging +# Function to safely remove registry values and keys function Remove-WSUSRegistry { param ( [string]$RegistryPath @@ -23,31 +24,60 @@ function Remove-WSUSRegistry { } try { - # Log what we're about to remove - $itemsToRemove = @() - Get-ChildItem -Path $RegistryPath -Recurse -ErrorAction SilentlyContinue | ForEach-Object { - $itemsToRemove += $_.PSPath - } + # Get all properties first for logging + $properties = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue + $wsusProperties = @() - # Get the properties in the root key - $rootProperties = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue - $propertyNames = ($rootProperties.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" }).Name + # List of WSUS-related property names to remove + $wsusValueNames = @( + 'WUServer', 'WUStatusServer', 'DoNotConnectToWindowsUpdateInternetLocations', + 'ElevateNonAdmins', 'TargetGroup', 'TargetGroupEnabled', 'UseWUServer', + 'AUOptions', 'AutoInstallMinorUpdates', 'DetectionFrequency', + 'DetectionFrequencyEnabled', 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', + 'RebootRelaunchTimeout', 'RebootWarningTimeout', 'RescheduleWaitTime', + 'ScheduledInstallDay', 'ScheduledInstallTime', 'FillEmptyContentUrls', + 'SetProxyBehaviorForUpdateDetection', 'UpdateServiceUrlAlternate', + 'UseUpdateClassPolicySource' + ) - if ($propertyNames) { - Write-Output "Removing properties from ${RegistryPath}: $($propertyNames -join ', ')" + # Check which WSUS properties exist + foreach ($valueName in $wsusValueNames) { + if ($null -ne $properties.$valueName) { + $wsusProperties += "$valueName = $($properties.$valueName)" + } } - if ($itemsToRemove) { - Write-Output "Removing subkeys: $($itemsToRemove -join '; ')" + if ($wsusProperties.Count -gt 0) { + Write-Output "Found WSUS properties in ${RegistryPath}: $($wsusProperties -join '; ')" + + # Remove individual WSUS properties + foreach ($valueName in $wsusValueNames) { + if ($null -ne $properties.$valueName) { + try { + Remove-ItemProperty -Path $RegistryPath -Name $valueName -Force -ErrorAction Stop + Write-Output "Removed property: $RegistryPath\$valueName" + } + catch { + Write-Output "Could not remove property $RegistryPath\$valueName - $($_.Exception.Message)" + } + } + } + } + + # Check if the key is now empty (only has default properties), if so remove it + $remainingProperties = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue + $nonDefaultProps = ($remainingProperties.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" }).Name + + if (-not $nonDefaultProps -or $nonDefaultProps.Count -eq 0) { + # Key is empty, remove it entirely + Remove-Item -Path $RegistryPath -Recurse -Force -ErrorAction Stop + Write-Output "Removed empty registry key: $RegistryPath" } - # Remove the entire key and all subkeys - Remove-Item -Path $RegistryPath -Recurse -Force -ErrorAction Stop - Write-Output "Successfully removed: $RegistryPath" return $true } catch { - Write-Output "ERROR: Failed to remove $RegistryPath - $($_.Exception.Message)" + Write-Output "ERROR: Failed to process $RegistryPath - $($_.Exception.Message)" return $false } } @@ -162,4 +192,4 @@ if ($allSuccess) { } else { Write-Output "Remediation completed with warnings. Manual verification may be required." exit 1 -} \ No newline at end of file +} From 8b17e7ca41c32fc88f651f606be382ccaf7d227d Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:47:19 -0400 Subject: [PATCH 075/107] Detect-IntuneUpdateSettings.ps1 --- Detect-IntuneUpdateSettings.ps1 | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Detect-IntuneUpdateSettings.ps1 b/Detect-IntuneUpdateSettings.ps1 index ad6164a..597ca38 100644 --- a/Detect-IntuneUpdateSettings.ps1 +++ b/Detect-IntuneUpdateSettings.ps1 @@ -11,11 +11,20 @@ $ErrorActionPreference = 'Stop' $wsusRegistryPaths = @( @{ Path = 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate' - Values = @('WUServer', 'WUStatusServer', 'DoNotConnectToWindowsUpdateInternetLocations', 'ElevateNonAdmins', 'TargetGroup', 'TargetGroupEnabled') + Values = @( + 'WUServer', 'WUStatusServer', 'DoNotConnectToWindowsUpdateInternetLocations', + 'ElevateNonAdmins', 'TargetGroup', 'TargetGroupEnabled', 'FillEmptyContentUrls', + 'SetProxyBehaviorForUpdateDetection', 'UpdateServiceUrlAlternate', 'UseUpdateClassPolicySource' + ) }, @{ Path = 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' - Values = @('UseWUServer', 'AUOptions', 'AutoInstallMinorUpdates', 'DetectionFrequency', 'DetectionFrequencyEnabled', 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', 'RebootRelaunchTimeout', 'RebootWarningTimeout', 'RescheduleWaitTime', 'ScheduledInstallDay', 'ScheduledInstallTime') + Values = @( + 'UseWUServer', 'AUOptions', 'AutoInstallMinorUpdates', 'DetectionFrequency', + 'DetectionFrequencyEnabled', 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', + 'RebootRelaunchTimeout', 'RebootWarningTimeout', 'RescheduleWaitTime', + 'ScheduledInstallDay', 'ScheduledInstallTime' + ) } ) @@ -72,4 +81,4 @@ catch { # Don't fail detection if we can't get this info } -exit 1 \ No newline at end of file +exit 1 From 8fc5b160da7087f72c70cff111dea816760ed6d4 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:40:33 -0400 Subject: [PATCH 076/107] Add files via upload Based on PatchMyPC Article settings. --- Detect-IntuneCoManageSettings.ps1 | 115 ++++++++++++++++ Remediate-IntuneCoManageSettings.ps1 | 187 +++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 Detect-IntuneCoManageSettings.ps1 create mode 100644 Remediate-IntuneCoManageSettings.ps1 diff --git a/Detect-IntuneCoManageSettings.ps1 b/Detect-IntuneCoManageSettings.ps1 new file mode 100644 index 0000000..dc1857d --- /dev/null +++ b/Detect-IntuneCoManageSettings.ps1 @@ -0,0 +1,115 @@ +# Intune Proactive Remediation - Detection Script +# Purpose: Detect co-management Windows Update workload issues that prevent Windows 10 to 11 upgrades +# Target: Co-managed devices with Windows Update workload set to Pilot/Intune + +$exitCode = 0 +$issues = @() + +try { + # Check if device is co-managed + $coMgmtPath = "HKLM:\SOFTWARE\Microsoft\DeviceManageabilityCSP\Provider\MS DM Server\FirstSyncStatus" + if (-not (Test-Path $coMgmtPath)) { + Write-Output "Device is not co-managed. Skipping remediation." + exit 0 + } + + # Check Windows Update workload capability (bit 3 = Windows Update Policies) + $capabilityPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" + $capability = Get-ItemProperty -Path $capabilityPath -Name "CoMgmtCapability" -ErrorAction SilentlyContinue + + if ($capability -and ($capability.CoMgmtCapability -band 4)) { + Write-Host "Windows Update workload is assigned to Intune (capability: $($capability.CoMgmtCapability))" + + # Issue 1: Check DisableDualScan setting (should be 0 when workload is moved to Intune) + $dualscanPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + $disableDualScan = Get-ItemProperty -Path $dualscanPath -Name "DisableDualScan" -ErrorAction SilentlyContinue + + if ($disableDualScan -and $disableDualScan.DisableDualScan -eq 1) { + $issues += "DisableDualScan is set to 1 (should be 0 for Intune workload)" + } + + # Issue 2: Check for tattooed NoAutoUpdate policy + $noAutoUpdatePath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" + $noAutoUpdate = Get-ItemProperty -Path $noAutoUpdatePath -Name "NoAutoUpdate" -ErrorAction SilentlyContinue + + if ($noAutoUpdate -and $noAutoUpdate.NoAutoUpdate -eq 1) { + $issues += "NoAutoUpdate is enabled (will disable automatic updates)" + } + + # Issue 3: Check Windows 11 Scan Source policies (if Windows 11) + $osVersion = [System.Environment]::OSVersion.Version + $buildNumber = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild + + if ($buildNumber -ge 22000) { # Windows 11 + Write-Host "Windows 11 detected - checking Scan Source policies" + + # Check if UseUpdateClassPolicySource is properly configured + $useUpdateClassPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" + $useUpdateClass = Get-ItemProperty -Path $useUpdateClassPath -Name "UseUpdateClassPolicySource" -ErrorAction SilentlyContinue + + if (-not $useUpdateClass -or $useUpdateClass.UseUpdateClassPolicySource -ne 1) { + $issues += "UseUpdateClassPolicySource is not properly configured for Windows 11" + } + + # Check Scan Source policies - these should be 0 when workload is moved to Intune + $scanSourcePolicies = @( + "PolicyDrivenUpdateSourceForFeatureUpdates", + "PolicyDrivenUpdateSourceForQualityUpdates", + "PolicyDrivenUpdateSourceForDriverUpdates", + "PolicyDrivenUpdateSourceForOtherUpdates" + ) + + foreach ($policy in $scanSourcePolicies) { + $value = Get-ItemProperty -Path $dualscanPath -Name $policy -ErrorAction SilentlyContinue + if ($value -and $value.$policy -ne 0) { + $issues += "$policy is set to $($value.$policy) (should be 0 for cloud updates)" + } + } + } + + # Issue 4: Check for conflicting WSUS server configuration when workload is moved to Intune + $wsusServer = Get-ItemProperty -Path $noAutoUpdatePath -Name "WUServer" -ErrorAction SilentlyContinue + $useWUServer = Get-ItemProperty -Path $noAutoUpdatePath -Name "UseWUServer" -ErrorAction SilentlyContinue + + if ($wsusServer -and $useWUServer -and $useWUServer.UseWUServer -eq 1) { + # This might be intentional for 3rd party updates, but flag for review + $issues += "WSUS server configuration detected - verify this is intentional for 3rd party updates only" + } + + # Issue 5: Check for Windows Update service disabled + $wuauserv = Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue + if ($wuauserv -and $wuauserv.StartType -eq "Disabled") { + $issues += "Windows Update service (wuauserv) is disabled" + } + + # Issue 6: Check for Feature Update deferral that might prevent Win11 upgrade + $featureUpdateDefer = Get-ItemProperty -Path $dualscanPath -Name "DeferFeatureUpdates" -ErrorAction SilentlyContinue + $deferDays = Get-ItemProperty -Path $dualscanPath -Name "DeferFeatureUpdatesPeriodInDays" -ErrorAction SilentlyContinue + + if ($featureUpdateDefer -and $featureUpdateDefer.DeferFeatureUpdates -eq 1 -and $deferDays -and $deferDays.DeferFeatureUpdatesPeriodInDays -gt 365) { + $issues += "Feature Updates deferred for more than 365 days (may prevent Windows 11 upgrade)" + } + + } else { + Write-Host "Windows Update workload is not assigned to Intune - no remediation needed" + exit 0 + } + + # Report findings + if ($issues.Count -gt 0) { + Write-Host "Found $($issues.Count) issues that may prevent Windows 10 to 11 upgrade:" + foreach ($issue in $issues) { + Write-Host "- $issue" + } + $exitCode = 1 + } else { + Write-Host "No Windows Update workload issues detected" + $exitCode = 0 + } + +} catch { + Write-Error "Error during detection: $($_.Exception.Message)" + $exitCode = 1 +} + +exit $exitCode \ No newline at end of file diff --git a/Remediate-IntuneCoManageSettings.ps1 b/Remediate-IntuneCoManageSettings.ps1 new file mode 100644 index 0000000..d9115f4 --- /dev/null +++ b/Remediate-IntuneCoManageSettings.ps1 @@ -0,0 +1,187 @@ +# Intune Proactive Remediation - Remediation Script +# Purpose: Fix co-management Windows Update workload issues that prevent Windows 10 to 11 upgrades +# Target: Co-managed devices with Windows Update workload set to Pilot/Intune + +$exitCode = 0 +$remediationActions = @() + +try { + # Verify device is co-managed and workload is assigned to Intune + $coMgmtPath = "HKLM:\SOFTWARE\Microsoft\DeviceManageabilityCSP\Provider\MS DM Server\FirstSyncStatus" + if (-not (Test-Path $coMgmtPath)) { + Write-Output "Device is not co-managed. Exiting." + exit 0 + } + + $capabilityPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" + $capability = Get-ItemProperty -Path $capabilityPath -Name "CoMgmtCapability" -ErrorAction SilentlyContinue + + if (-not $capability -or -not ($capability.CoMgmtCapability -band 4)) { + Write-Output "Windows Update workload is not assigned to Intune. Exiting." + exit 0 + } + + Write-Host "Starting remediation for co-managed device with Windows Update workload assigned to Intune" + + # Registry paths + $dualscanPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + $auPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" + + # Ensure registry paths exist + if (-not (Test-Path $dualscanPath)) { + New-Item -Path $dualscanPath -Force | Out-Null + } + if (-not (Test-Path $auPath)) { + New-Item -Path $auPath -Force | Out-Null + } + + # Remediation 1: Fix DisableDualScan setting + $disableDualScan = Get-ItemProperty -Path $dualscanPath -Name "DisableDualScan" -ErrorAction SilentlyContinue + if ($disableDualScan -and $disableDualScan.DisableDualScan -eq 1) { + Set-ItemProperty -Path $dualscanPath -Name "DisableDualScan" -Value 0 -Type DWord + $remediationActions += "Set DisableDualScan to 0 (enabled dual scan for Intune workload)" + } + + # Remediation 2: Remove tattooed NoAutoUpdate policy + $noAutoUpdate = Get-ItemProperty -Path $auPath -Name "NoAutoUpdate" -ErrorAction SilentlyContinue + if ($noAutoUpdate -and $noAutoUpdate.NoAutoUpdate -eq 1) { + Remove-ItemProperty -Path $auPath -Name "NoAutoUpdate" -ErrorAction SilentlyContinue + $remediationActions += "Removed NoAutoUpdate registry setting" + } + + # Get OS version info + $buildNumber = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild + + if ($buildNumber -ge 22000) { # Windows 11 + Write-Host "Applying Windows 11 specific remediations" + + # Remediation 3: Configure UseUpdateClassPolicySource for Windows 11 + Set-ItemProperty -Path $auPath -Name "UseUpdateClassPolicySource" -Value 1 -Type DWord + $remediationActions += "Set UseUpdateClassPolicySource to 1 for Windows 11" + + # Remediation 4: Configure Scan Source policies for cloud updates + $scanSourcePolicies = @( + "PolicyDrivenUpdateSourceForFeatureUpdates", + "PolicyDrivenUpdateSourceForQualityUpdates", + "PolicyDrivenUpdateSourceForDriverUpdates", + "PolicyDrivenUpdateSourceForOtherUpdates" + ) + + foreach ($policy in $scanSourcePolicies) { + $currentValue = Get-ItemProperty -Path $dualscanPath -Name $policy -ErrorAction SilentlyContinue + if (-not $currentValue -or $currentValue.$policy -ne 0) { + Set-ItemProperty -Path $dualscanPath -Name $policy -Value 0 -Type DWord + $remediationActions += "Set $policy to 0 (cloud source)" + } + } + + # Remediation 5: Remove any Group Policy tattooed scan source policies + $gpRegPath = "C:\Windows\System32\GroupPolicy\Machine\Registry.pol" + if (Test-Path $gpRegPath) { + try { + # Force a group policy refresh to clear any local computer policies + & gpupdate /force /target:computer + $remediationActions += "Forced Group Policy refresh to clear local computer policies" + } catch { + Write-Warning "Could not force Group Policy refresh: $($_.Exception.Message)" + } + } + } else { # Windows 10 + Write-Host "Applying Windows 10 specific remediations" + + # For Windows 10, ensure dual scan is properly enabled when workload is moved to Intune + # The DisableDualScan should already be set to 0 above + } + + # Remediation 6: Ensure Windows Update service is properly configured + $wuauserv = Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue + if ($wuauserv) { + if ($wuauserv.StartType -eq "Disabled") { + Set-Service -Name "wuauserv" -StartupType Manual + $remediationActions += "Changed Windows Update service startup type from Disabled to Manual" + } + + if ($wuauserv.Status -ne "Running") { + try { + Start-Service -Name "wuauserv" -ErrorAction Stop + $remediationActions += "Started Windows Update service" + } catch { + Write-Warning "Could not start Windows Update service: $($_.Exception.Message)" + } + } + } + + # Remediation 7: Reset Windows Update components if needed + if ($remediationActions.Count -gt 0) { + try { + # Stop Windows Update services + Stop-Service -Name "wuauserv" -Force -ErrorAction SilentlyContinue + Stop-Service -Name "cryptSvc" -Force -ErrorAction SilentlyContinue + Stop-Service -Name "bits" -Force -ErrorAction SilentlyContinue + Stop-Service -Name "msiserver" -Force -ErrorAction SilentlyContinue + + # Clear Windows Update cache + if (Test-Path "$env:WINDIR\SoftwareDistribution") { + Remove-Item "$env:WINDIR\SoftwareDistribution\*" -Recurse -Force -ErrorAction SilentlyContinue + } + + # Restart services + Start-Service -Name "cryptSvc" -ErrorAction SilentlyContinue + Start-Service -Name "bits" -ErrorAction SilentlyContinue + Start-Service -Name "wuauserv" -ErrorAction SilentlyContinue + + $remediationActions += "Reset Windows Update components and cache" + } catch { + Write-Warning "Could not reset Windows Update components: $($_.Exception.Message)" + } + } + + # Remediation 8: Trigger Configuration Manager client actions to re-evaluate policies + try { + $ccmPath = (Get-WmiObject -Namespace "root\ccm" -Class "SMS_Client" -ErrorAction SilentlyContinue).ClientVersion + if ($ccmPath) { + # Trigger co-management policy evaluation + Invoke-WmiMethod -Namespace "root\ccm" -Class "SMS_Client" -Name "TriggerSchedule" -ArgumentList "{00000000-0000-0000-0000-000000000032}" -ErrorAction SilentlyContinue + # Trigger Windows Update policy evaluation + Invoke-WmiMethod -Namespace "root\ccm" -Class "SMS_Client" -Name "TriggerSchedule" -ArgumentList "{00000000-0000-0000-0000-000000000113}" -ErrorAction SilentlyContinue + $remediationActions += "Triggered Configuration Manager policy evaluation" + } + } catch { + Write-Warning "Could not trigger Configuration Manager policy evaluation: $($_.Exception.Message)" + } + + # Remediation 9: Force Windows Update detection cycle + try { + $updateSession = New-Object -ComObject "Microsoft.Update.Session" + $updateSearcher = $updateSession.CreateUpdateSearcher() + $updateSearcher.Online = $true + $null = $updateSearcher.Search("IsInstalled=0") + $remediationActions += "Forced Windows Update detection cycle" + } catch { + Write-Warning "Could not force Windows Update detection: $($_.Exception.Message)" + } + + # Report remediation actions taken + if ($remediationActions.Count -gt 0) { + Write-Host "Remediation completed. Actions taken:" + foreach ($action in $remediationActions) { + Write-Host "✓ $action" + } + Write-Host "" + Write-Host "Recommendations:" + Write-Host "1. Monitor Windows Update logs for proper functionality" + Write-Host "2. Verify Windows 11 upgrade eligibility in Windows Update settings" + Write-Host "3. Check Intune Windows Update policies are properly targeted" + Write-Host "4. Consider using Windows Update for Business deployment service for better control" + $exitCode = 0 + } else { + Write-Host "No remediation actions were needed" + $exitCode = 0 + } + +} catch { + Write-Error "Error during remediation: $($_.Exception.Message)" + $exitCode = 1 +} + +exit $exitCode \ No newline at end of file From 4cea8023ece325702affc9dd20c4c851a99ae68a Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:50:48 -0400 Subject: [PATCH 077/107] Detect-IntuneCoManageSettings.ps1 --- Detect-IntuneCoManageSettings.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Detect-IntuneCoManageSettings.ps1 b/Detect-IntuneCoManageSettings.ps1 index dc1857d..4815425 100644 --- a/Detect-IntuneCoManageSettings.ps1 +++ b/Detect-IntuneCoManageSettings.ps1 @@ -8,10 +8,10 @@ $issues = @() try { # Check if device is co-managed $coMgmtPath = "HKLM:\SOFTWARE\Microsoft\DeviceManageabilityCSP\Provider\MS DM Server\FirstSyncStatus" - if (-not (Test-Path $coMgmtPath)) { - Write-Output "Device is not co-managed. Skipping remediation." - exit 0 - } + # if (-not (Test-Path $coMgmtPath)) { + # Write-Output "Device is not co-managed. Skipping remediation." + # exit 0 + # } # Check Windows Update workload capability (bit 3 = Windows Update Policies) $capabilityPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" @@ -112,4 +112,5 @@ try { $exitCode = 1 } -exit $exitCode \ No newline at end of file + +exit $exitCode From 8f11313567be5bc2dcff423b61f903a0ac74ac38 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:56:05 -0400 Subject: [PATCH 078/107] Detect-IntuneCoManageSettings.ps1 --- Detect-IntuneCoManageSettings.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Detect-IntuneCoManageSettings.ps1 b/Detect-IntuneCoManageSettings.ps1 index 4815425..a5cf07c 100644 --- a/Detect-IntuneCoManageSettings.ps1 +++ b/Detect-IntuneCoManageSettings.ps1 @@ -91,8 +91,8 @@ try { } } else { - Write-Host "Windows Update workload is not assigned to Intune - no remediation needed" - exit 0 + Write-Host "Windows Update workload is not assigned to Intune - will remediate" + exit 1 } # Report findings @@ -114,3 +114,4 @@ try { exit $exitCode + From 672488595aba879d0cc20faa16bd7fae8284d9c8 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:58:50 -0400 Subject: [PATCH 079/107] Remediate-IntuneCoManageSettings.ps1 --- Remediate-IntuneCoManageSettings.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Remediate-IntuneCoManageSettings.ps1 b/Remediate-IntuneCoManageSettings.ps1 index d9115f4..b8c281c 100644 --- a/Remediate-IntuneCoManageSettings.ps1 +++ b/Remediate-IntuneCoManageSettings.ps1 @@ -138,8 +138,8 @@ try { # Remediation 8: Trigger Configuration Manager client actions to re-evaluate policies try { - $ccmPath = (Get-WmiObject -Namespace "root\ccm" -Class "SMS_Client" -ErrorAction SilentlyContinue).ClientVersion - if ($ccmPath) { + $ccmClient = Get-WmiObject -Namespace "root\ccm" -Class "SMS_Client" -ErrorAction SilentlyContinue + if ($ccmClient) { # Trigger co-management policy evaluation Invoke-WmiMethod -Namespace "root\ccm" -Class "SMS_Client" -Name "TriggerSchedule" -ArgumentList "{00000000-0000-0000-0000-000000000032}" -ErrorAction SilentlyContinue # Trigger Windows Update policy evaluation @@ -155,7 +155,7 @@ try { $updateSession = New-Object -ComObject "Microsoft.Update.Session" $updateSearcher = $updateSession.CreateUpdateSearcher() $updateSearcher.Online = $true - $null = $updateSearcher.Search("IsInstalled=0") + $searchResult = $updateSearcher.Search("IsInstalled=0") $remediationActions += "Forced Windows Update detection cycle" } catch { Write-Warning "Could not force Windows Update detection: $($_.Exception.Message)" @@ -165,7 +165,7 @@ try { if ($remediationActions.Count -gt 0) { Write-Host "Remediation completed. Actions taken:" foreach ($action in $remediationActions) { - Write-Host "✓ $action" + Write-Host "- $action" } Write-Host "" Write-Host "Recommendations:" @@ -184,4 +184,4 @@ try { $exitCode = 1 } -exit $exitCode \ No newline at end of file +exit $exitCode From eb4b3e736e8a4d5850d63e22649b577ec4ecf58e Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:01:04 -0400 Subject: [PATCH 080/107] Remediate-IntuneCoManageSettings.ps1 --- Remediate-IntuneCoManageSettings.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Remediate-IntuneCoManageSettings.ps1 b/Remediate-IntuneCoManageSettings.ps1 index b8c281c..589106f 100644 --- a/Remediate-IntuneCoManageSettings.ps1 +++ b/Remediate-IntuneCoManageSettings.ps1 @@ -7,11 +7,11 @@ $remediationActions = @() try { # Verify device is co-managed and workload is assigned to Intune - $coMgmtPath = "HKLM:\SOFTWARE\Microsoft\DeviceManageabilityCSP\Provider\MS DM Server\FirstSyncStatus" - if (-not (Test-Path $coMgmtPath)) { - Write-Output "Device is not co-managed. Exiting." - exit 0 - } + # $coMgmtPath = "HKLM:\SOFTWARE\Microsoft\DeviceManageabilityCSP\Provider\MS DM Server\FirstSyncStatus" + # if (-not (Test-Path $coMgmtPath)) { + # Write-Output "Device is not co-managed. Exiting." + # exit 0 + # } $capabilityPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" $capability = Get-ItemProperty -Path $capabilityPath -Name "CoMgmtCapability" -ErrorAction SilentlyContinue @@ -185,3 +185,4 @@ try { } exit $exitCode + From 64d30413157e58707b9a5e796199ccaffcb2a0d5 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:03:45 -0400 Subject: [PATCH 081/107] Remediate-IntuneCoManageSettings.ps1 --- Remediate-IntuneCoManageSettings.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Remediate-IntuneCoManageSettings.ps1 b/Remediate-IntuneCoManageSettings.ps1 index 589106f..079a3c9 100644 --- a/Remediate-IntuneCoManageSettings.ps1 +++ b/Remediate-IntuneCoManageSettings.ps1 @@ -17,11 +17,11 @@ try { $capability = Get-ItemProperty -Path $capabilityPath -Name "CoMgmtCapability" -ErrorAction SilentlyContinue if (-not $capability -or -not ($capability.CoMgmtCapability -band 4)) { - Write-Output "Windows Update workload is not assigned to Intune. Exiting." - exit 0 + Write-Output "Windows Update workload is not showing as assigned to Intune. Will continue with remediation steps." + # exit 0 } - Write-Host "Starting remediation for co-managed device with Windows Update workload assigned to Intune" + Write-Host "Starting remediation for device" # Registry paths $dualscanPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" @@ -186,3 +186,4 @@ try { exit $exitCode + From 52c37c787bfec777ed133a6fa9ef56dae1fc204f Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:13:55 -0400 Subject: [PATCH 082/107] Remediate-IntuneCoManageSettings.ps1 --- Remediate-IntuneCoManageSettings.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Remediate-IntuneCoManageSettings.ps1 b/Remediate-IntuneCoManageSettings.ps1 index 079a3c9..ab1d5d6 100644 --- a/Remediate-IntuneCoManageSettings.ps1 +++ b/Remediate-IntuneCoManageSettings.ps1 @@ -61,10 +61,10 @@ try { # Remediation 4: Configure Scan Source policies for cloud updates $scanSourcePolicies = @( - "PolicyDrivenUpdateSourceForFeatureUpdates", - "PolicyDrivenUpdateSourceForQualityUpdates", - "PolicyDrivenUpdateSourceForDriverUpdates", - "PolicyDrivenUpdateSourceForOtherUpdates" + "SetPolicyDrivenUpdateSourceForFeatureUpdates", + "SetPolicyDrivenUpdateSourceForQualityUpdates", + "SetPolicyDrivenUpdateSourceForDriverUpdates", + "SetPolicyDrivenUpdateSourceForOtherUpdates" ) foreach ($policy in $scanSourcePolicies) { @@ -187,3 +187,4 @@ try { exit $exitCode + From ff0cb3bf7636ba5ce0490adc732193e7eeba99cf Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:45:23 -0400 Subject: [PATCH 083/107] Create-StaleDeviceGroup.ps1 Creates or Adds to a named device group for stale device records based on a specific number of days offline. --- Create-StaleDeviceGroup.ps1 | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Create-StaleDeviceGroup.ps1 diff --git a/Create-StaleDeviceGroup.ps1 b/Create-StaleDeviceGroup.ps1 new file mode 100644 index 0000000..29f19a6 --- /dev/null +++ b/Create-StaleDeviceGroup.ps1 @@ -0,0 +1,60 @@ +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "Group.ReadWrite.All", "Device.Read.All", "GroupMember.ReadWrite.All" + +# Configuration +$DaysInactive = 90 +$CutoffDate = (Get-Date).AddDays(-$DaysInactive) +$GroupName = "Stale Device Records" +$GroupDescription = "Devices inactive for $DaysInactive+ days - Created $(Get-Date -Format 'yyyy-MM-dd')" + +# Check if group already exists +$ExistingGroup = Get-MgGroup -Filter "displayName eq '$GroupName'" -ErrorAction SilentlyContinue + +if ($ExistingGroup) { + Write-Host "Using existing group: $GroupName" -ForegroundColor Yellow + $StaleDeviceGroup = $ExistingGroup +} else { + # Create new security group + $GroupParams = @{ + DisplayName = $GroupName + Description = $GroupDescription + GroupTypes = @() # Empty array creates security group + MailEnabled = $false + SecurityEnabled = $true + MailNickname = "StaleDeviceRecords" + } + + $StaleDeviceGroup = New-MgGroup @GroupParams + Write-Host "Created group: $GroupName (ID: $($StaleDeviceGroup.Id))" -ForegroundColor Green +} + +# Get stale devices +$StaleDevices = Get-MgDevice -All | Where-Object { + ($_.ApproximateLastSignInDateTime -lt $CutoffDate) -or + ($_.ApproximateLastSignInDateTime -eq $null -and $_.RegistrationDateTime -lt $CutoffDate) +} + +Write-Host "Found $($StaleDevices.Count) stale devices" + +# Get current group members to avoid duplicates +$CurrentMembers = Get-MgGroupMember -GroupId $StaleDeviceGroup.Id -All + +# Add devices to group +$AddedCount = 0 +foreach ($Device in $StaleDevices) { + # Check if device is already a member + if ($CurrentMembers.Id -notcontains $Device.Id) { + try { + New-MgGroupMember -GroupId $StaleDeviceGroup.Id -DirectoryObjectId $Device.Id + Write-Host "➕ Added: $($Device.DisplayName)" -ForegroundColor Green + $AddedCount++ + } + catch { + Write-Host "Failed to add: $($Device.DisplayName) - $($_.Exception.Message)" -ForegroundColor Red + } + } else { + Write-Host "Already member: $($Device.DisplayName)" -ForegroundColor Gray + } +} + +Write-Host "Added $AddedCount new devices to group '$GroupName'" From 3c97724450850d90096d392ac5b7e2930aa113fe Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:51:20 -0400 Subject: [PATCH 084/107] Delete-StaleDeviceByGroup.ps1 Named device group for stale device records will be deleted. Includes Dry-Run option to check first. --- Delete-StaleDeviceByGroup.ps1 | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Delete-StaleDeviceByGroup.ps1 diff --git a/Delete-StaleDeviceByGroup.ps1 b/Delete-StaleDeviceByGroup.ps1 new file mode 100644 index 0000000..222d485 --- /dev/null +++ b/Delete-StaleDeviceByGroup.ps1 @@ -0,0 +1,54 @@ +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "Group.Read.All", "Device.ReadWrite.All", "GroupMember.Read.All" + +# Configuration +$GroupName = "Stale Device Records" +$DryRun = $true # Set to $false to actually delete devices + +# Get the stale device group +$StaleGroup = Get-MgGroup -Filter "displayName eq '$GroupName'" +if (-not $StaleGroup) { + Write-Error "Group '$GroupName' not found!" + exit 1 +} + +Write-Host "Found group: $($StaleGroup.DisplayName) (ID: $($StaleGroup.Id))" + +# Get all device members of the group +$GroupMembers = Get-MgGroupMember -GroupId $StaleGroup.Id -All +$DeviceMembers = $GroupMembers | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.device' } + +Write-Host "Found $($DeviceMembers.Count) devices in the group" + +# Process each device +$SuccessCount = 0 +$FailCount = 0 + +foreach ($Member in $DeviceMembers) { + try { + $Device = Get-MgDevice -DeviceId $Member.Id + Write-Host "Processing: $($Device.DisplayName) | OS: $($Device.OperatingSystem) | Last Seen: $($Device.ApproximateLastSignInDateTime)" + + if (-not $DryRun) { + # Delete the device from Entra ID + Remove-MgDevice -DeviceId $Device.Id -Confirm:$false + Write-Host "Deleted: $($Device.DisplayName)" -ForegroundColor Green + $SuccessCount++ + } else { + Write-Host "[DRY RUN] Would delete: $($Device.DisplayName)" -ForegroundColor Yellow + } + } + catch { + Write-Host "Failed to delete: $($Device.DisplayName) - $($_.Exception.Message)" -ForegroundColor Red + $FailCount++ + } +} + +# Summary +if ($DryRun) { + Write-Host "`n DRY RUN SUMMARY: Would delete $($DeviceMembers.Count) devices" +} else { + Write-Host "`n DELETION SUMMARY:" + Write-Host " Successfully deleted: $SuccessCount devices" + Write-Host " Failed to delete: $FailCount devices" +} From 133dac4c19fc6ca1e48ae38a356ae65568c4214a Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:03:56 -0400 Subject: [PATCH 085/107] Sysprep-PreCleanup.ps1 --- Sysprep-PreCleanup.ps1 | 269 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 Sysprep-PreCleanup.ps1 diff --git a/Sysprep-PreCleanup.ps1 b/Sysprep-PreCleanup.ps1 new file mode 100644 index 0000000..865bcba --- /dev/null +++ b/Sysprep-PreCleanup.ps1 @@ -0,0 +1,269 @@ +# Windows 11 Sysprep Cleanup Script +# This script removes problematic AppX packages that commonly cause Sysprep failures +# Run as Administrator + +#Requires -RunAsAdministrator + +param( + [switch]$WhatIf, + [switch]$Verbose, + [string]$LogPath = "$env:TEMP\SysprepCleanup.log" +) + +# Initialize logging +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logEntry = "[$timestamp] [$Level] $Message" + Write-Host $logEntry + Add-Content -Path $LogPath -Value $logEntry +} + +Write-Log "Starting Sysprep cleanup process" "INFO" +Write-Log "Log file: $LogPath" "INFO" + +if ($WhatIf) { + Write-Log "Running in WhatIf mode - no changes will be made" "WARN" +} + +# List of problematic AppX packages that commonly cause Sysprep failures +$ProblematicApps = @( + "Microsoft.XboxApp", + "Microsoft.XboxGameOverlay", + "Microsoft.XboxGamingOverlay", + "Microsoft.XboxIdentityProvider", + "Microsoft.XboxSpeechToTextOverlay", + "Microsoft.Xbox.TCUI", + "Microsoft.XboxGameCallableUI", + "Microsoft.549981C3F5F10", # Cortana + "Cortana", + "Microsoft.BingWeather", + "Microsoft.GetHelp", + "Microsoft.Getstarted", + "Microsoft.MicrosoftOfficeHub", + "Microsoft.MicrosoftSolitaireCollection", + "Microsoft.People", + "Microsoft.WindowsCamera", + "Microsoft.windowscommunicationsapps", # Mail and Calendar + "Microsoft.WindowsFeedbackHub", + "Microsoft.WindowsMaps", + "Microsoft.WindowsSoundRecorder", + "Microsoft.YourPhone", + "Microsoft.ZuneMusic", + "Microsoft.ZuneVideo", + "Microsoft.SkypeApp", + "Microsoft.MicrosoftStickyNotes", + "Microsoft.MSPaint", + "Microsoft.Microsoft3DViewer", + "Microsoft.MixedReality.Portal", + "Microsoft.OneConnect", + "Microsoft.Print3D", + "Microsoft.Wallet" +) + +# Function to remove AppX packages for all users +function Remove-AppXPackages { + param([string[]]$AppList) + + Write-Log "Removing AppX packages for all users..." "INFO" + + foreach ($app in $AppList) { + try { + Write-Log "Processing: $app" "INFO" + + # Get all AppX packages for current user + $userPackages = Get-AppxPackage -Name "*$app*" -ErrorAction SilentlyContinue + foreach ($package in $userPackages) { + if ($WhatIf) { + Write-Log "WhatIf: Would remove user package: $($package.Name)" "WARN" + } else { + Write-Log "Removing user package: $($package.Name)" "INFO" + Remove-AppxPackage -Package $package.PackageFullName -ErrorAction SilentlyContinue + } + } + + # Get all AppX packages for all users + $allUserPackages = Get-AppxPackage -AllUsers -Name "*$app*" -ErrorAction SilentlyContinue + foreach ($package in $allUserPackages) { + if ($WhatIf) { + Write-Log "WhatIf: Would remove all-users package: $($package.Name)" "WARN" + } else { + Write-Log "Removing all-users package: $($package.Name)" "INFO" + Remove-AppxPackage -Package $package.PackageFullName -AllUsers -ErrorAction SilentlyContinue + } + } + + } catch { + Write-Log "Error processing $app : $($_.Exception.Message)" "ERROR" + } + } +} + +# Function to remove provisioned AppX packages +function Remove-ProvisionedPackages { + param([string[]]$AppList) + + Write-Log "Removing provisioned AppX packages..." "INFO" + + foreach ($app in $AppList) { + try { + $provisionedPackages = Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -like "*$app*" } + foreach ($package in $provisionedPackages) { + if ($WhatIf) { + Write-Log "WhatIf: Would remove provisioned package: $($package.DisplayName)" "WARN" + } else { + Write-Log "Removing provisioned package: $($package.DisplayName)" "INFO" + Remove-AppxProvisionedPackage -Online -PackageName $package.PackageName -ErrorAction SilentlyContinue + } + } + } catch { + Write-Log "Error processing provisioned package $app : $($_.Exception.Message)" "ERROR" + } + } +} + +# Function to clean up Windows capabilities that might cause issues +function Remove-ProblematicCapabilities { + Write-Log "Checking for problematic Windows capabilities..." "INFO" + + $ProblematicCapabilities = @( + "XPS.Viewer~~~~0.0.1.0", + "Print.Fax.Scan~~~~0.0.1.0" + ) + + foreach ($capability in $ProblematicCapabilities) { + try { + $cap = Get-WindowsCapability -Online -Name $capability -ErrorAction SilentlyContinue + if ($cap -and $cap.State -eq "Installed") { + if ($WhatIf) { + Write-Log "WhatIf: Would remove capability: $capability" "WARN" + } else { + Write-Log "Removing capability: $capability" "INFO" + Remove-WindowsCapability -Online -Name $capability -ErrorAction SilentlyContinue + } + } + } catch { + Write-Log "Error processing capability $capability : $($_.Exception.Message)" "ERROR" + } + } +} + +# Function to clean up Edge WebView2 issues +function Fix-EdgeWebView { + Write-Log "Checking Edge WebView2 installations..." "INFO" + + try { + # Remove Edge WebView2 from all users if it exists + $webViewPackages = Get-AppxPackage -AllUsers -Name "*WebExperience*" -ErrorAction SilentlyContinue + foreach ($package in $webViewPackages) { + if ($WhatIf) { + Write-Log "WhatIf: Would remove WebView package: $($package.Name)" "WARN" + } else { + Write-Log "Removing WebView package: $($package.Name)" "INFO" + Remove-AppxPackage -Package $package.PackageFullName -AllUsers -ErrorAction SilentlyContinue + } + } + } catch { + Write-Log "Error processing Edge WebView: $($_.Exception.Message)" "ERROR" + } +} + +# Function to clean up Windows Update medic service cache +function Clear-UpdateMedicCache { + Write-Log "Clearing Windows Update Medic Service cache..." "INFO" + + try { + $serviceName = "WaaSMedicSvc" + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + + if ($service) { + if ($WhatIf) { + Write-Log "WhatIf: Would stop $serviceName service" "WARN" + } else { + Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue + Write-Log "Stopped $serviceName service" "INFO" + } + } + + # Clear CBS logs that can cause issues + $cbsLogPath = "$env:SystemRoot\Logs\CBS" + if (Test-Path $cbsLogPath) { + if ($WhatIf) { + Write-Log "WhatIf: Would clear CBS logs at $cbsLogPath" "WARN" + } else { + Get-ChildItem -Path $cbsLogPath -Filter "*.log" | Remove-Item -Force -ErrorAction SilentlyContinue + Write-Log "Cleared CBS logs" "INFO" + } + } + + } catch { + Write-Log "Error clearing Update Medic cache: $($_.Exception.Message)" "ERROR" + } +} + +# Function to run DISM cleanup +function Invoke-DismCleanup { + Write-Log "Running DISM cleanup operations..." "INFO" + + try { + if ($WhatIf) { + Write-Log "WhatIf: Would run DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase" "WARN" + } else { + Write-Log "Running DISM component cleanup..." "INFO" + & dism.exe /Online /Cleanup-Image /StartComponentCleanup /ResetBase + Write-Log "DISM cleanup completed" "INFO" + } + } catch { + Write-Log "Error running DISM cleanup: $($_.Exception.Message)" "ERROR" + } +} + +# Main execution +try { + Write-Log "=== STARTING SYSPREP CLEANUP ===" "INFO" + + # Remove problematic AppX packages + Remove-AppXPackages -AppList $ProblematicApps + + # Remove provisioned packages + Remove-ProvisionedPackages -AppList $ProblematicApps + + # Handle Edge WebView issues + Fix-EdgeWebView + + # Remove problematic capabilities + Remove-ProblematicCapabilities + + # Clear Windows Update Medic cache + Clear-UpdateMedicCache + + # Run DISM cleanup + Invoke-DismCleanup + + Write-Log "=== SYSPREP CLEANUP COMPLETED ===" "INFO" + Write-Log "Please review the log for any errors before running Sysprep" "WARN" + Write-Log "Recommended: Restart the system before running Sysprep" "WARN" + + # Display summary + Write-Host "`n" -ForegroundColor Green + Write-Host "Sysprep Cleanup Summary:" -ForegroundColor Green + Write-Host "- Removed problematic AppX packages" -ForegroundColor Yellow + Write-Host "- Cleaned provisioned app packages" -ForegroundColor Yellow + Write-Host "- Addressed Edge WebView2 issues" -ForegroundColor Yellow + Write-Host "- Removed problematic Windows capabilities" -ForegroundColor Yellow + Write-Host "- Cleared Windows Update caches" -ForegroundColor Yellow + Write-Host "- Ran DISM component cleanup" -ForegroundColor Yellow + Write-Host "`nLog file saved to: $LogPath" -ForegroundColor Cyan + Write-Host "`nIMPORTANT: Restart the system before running Sysprep!" -ForegroundColor Red + +} catch { + Write-Log "Critical error during cleanup: $($_.Exception.Message)" "ERROR" + Write-Host "Critical error occurred. Check the log file: $LogPath" -ForegroundColor Red + exit 1 +} + +# Usage examples: +# .\SysprepCleanup.ps1 # Run normally +# .\SysprepCleanup.ps1 -WhatIf # Preview what would be done +# .\SysprepCleanup.ps1 -Verbose # Verbose output +# .\SysprepCleanup.ps1 -LogPath "C:\Logs\cleanup.log" # Custom log location From a6d8dff68087a49621a20eb8a3a114f7bc49a353 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:52:03 -0400 Subject: [PATCH 086/107] Cleanup-Apps4Sysprep.ps1 --- Cleanup-Apps4Sysprep.ps1 | 174 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 Cleanup-Apps4Sysprep.ps1 diff --git a/Cleanup-Apps4Sysprep.ps1 b/Cleanup-Apps4Sysprep.ps1 new file mode 100644 index 0000000..9c2d082 --- /dev/null +++ b/Cleanup-Apps4Sysprep.ps1 @@ -0,0 +1,174 @@ +# Enhanced AppX Cleanup Script for Sysprep Preparation +# Removes all AppX packages and provisioned packages that can block sysprep +# Run as Administrator + +# Set execution policy and error handling +Set-ExecutionPolicy Bypass -Scope Process -Force +$ErrorActionPreference = "Continue" + +# Create log file +$LogPath = "C:\Windows\Temp\AppX_Cleanup.log" +$StartTime = Get-Date +Write-Output "=== AppX Cleanup Script Started: $StartTime ===" | Tee-Object -FilePath $LogPath -Append + +function Write-LogMessage { + param([string]$Message) + $TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $LogEntry = "[$TimeStamp] $Message" + Write-Output $LogEntry | Tee-Object -FilePath $LogPath -Append +} + +Write-LogMessage "Starting comprehensive AppX package removal..." + +# Stop Windows Update service to prevent interference +Write-LogMessage "Stopping Windows Update service..." +Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue + +# Remove ALL AppX packages for current user (except critical system components) +Write-LogMessage "Removing AppX packages for current user..." +$CriticalPackages = @( + "Microsoft.WindowsStore", + "Microsoft.DesktopAppInstaller", + "Microsoft.VCLibs*", + "Microsoft.NET.Native*", + "Microsoft.UI.Xaml*" +) + +try { + $AllPackages = Get-AppxPackage | Where-Object { + $package = $_ + -not ($CriticalPackages | Where-Object { $package.Name -like $_ }) + } + + foreach ($Package in $AllPackages) { + try { + Write-LogMessage "Removing package: $($Package.Name)" + Remove-AppxPackage -Package $Package.PackageFullName -ErrorAction Stop + Write-LogMessage "Successfully removed: $($Package.Name)" + } + catch { + Write-LogMessage "Failed to remove $($Package.Name): $($_.Exception.Message)" + } + } +} +catch { + Write-LogMessage "Error getting AppX packages: $($_.Exception.Message)" +} + +# Remove ALL AppX packages for ALL users +Write-LogMessage "Removing AppX packages for all users..." +try { + $AllUserPackages = Get-AppxPackage -AllUsers | Where-Object { + $package = $_ + -not ($CriticalPackages | Where-Object { $package.Name -like $_ }) + } + + foreach ($Package in $AllUserPackages) { + try { + Write-LogMessage "Removing package for all users: $($Package.Name)" + Remove-AppxPackage -Package $Package.PackageFullName -AllUsers -ErrorAction Stop + Write-LogMessage "Successfully removed for all users: $($Package.Name)" + } + catch { + Write-LogMessage "Failed to remove for all users $($Package.Name): $($_.Exception.Message)" + } + } +} +catch { + Write-LogMessage "Error getting AppX packages for all users: $($_.Exception.Message)" +} + +# Remove ALL provisioned AppX packages +Write-LogMessage "Removing provisioned AppX packages..." +try { + $ProvisionedPackages = Get-AppxProvisionedPackage -Online | Where-Object { + $package = $_ + -not ($CriticalPackages | Where-Object { $package.DisplayName -like $_ }) + } + + foreach ($Package in $ProvisionedPackages) { + try { + Write-LogMessage "Removing provisioned package: $($Package.DisplayName)" + Remove-AppxProvisionedPackage -Online -PackageName $Package.PackageName -ErrorAction Stop + Write-LogMessage "Successfully removed provisioned: $($Package.DisplayName)" + } + catch { + Write-LogMessage "Failed to remove provisioned $($Package.DisplayName): $($_.Exception.Message)" + } + } +} +catch { + Write-LogMessage "Error getting provisioned packages: $($_.Exception.Message)" +} + +# Clean up AppX deployment cache +Write-LogMessage "Cleaning AppX deployment cache..." +try { + $AppXCachePaths = @( + "$env:LOCALAPPDATA\Packages", + "$env:LOCALAPPDATA\Microsoft\Windows\INetCache", + "$env:TEMP\*.appx", + "C:\Windows\System32\AppLocker\*.appx" + ) + + foreach ($Path in $AppXCachePaths) { + if (Test-Path $Path) { + Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue + Write-LogMessage "Cleaned cache path: $Path" + } + } +} +catch { + Write-LogMessage "Error cleaning cache: $($_.Exception.Message)" +} + +# Clean Windows Store cache +Write-LogMessage "Cleaning Windows Store cache..." +try { + Start-Process wsreset.exe -WindowStyle Hidden -Wait -ErrorAction SilentlyContinue + Write-LogMessage "Windows Store cache reset completed" +} +catch { + Write-LogMessage "Error resetting Windows Store cache: $($_.Exception.Message)" +} + +# Remove AppX maintenance tasks that can interfere with sysprep +Write-LogMessage "Disabling AppX maintenance tasks..." +$TasksToDisable = @( + "\Microsoft\Windows\AppxDeploymentClient\*", + "\Microsoft\Windows\ApplicationData\*" +) + +foreach ($TaskPath in $TasksToDisable) { + try { + Get-ScheduledTask -TaskPath $TaskPath -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue + Write-LogMessage "Disabled scheduled tasks: $TaskPath" + } + catch { + Write-LogMessage "Could not disable tasks: $TaskPath" + } +} + +# Restart Windows Update service +Write-LogMessage "Restarting Windows Update service..." +Start-Service -Name wuauserv -ErrorAction SilentlyContinue + +# Final verification +Write-LogMessage "Performing final verification..." +$RemainingPackages = Get-AppxPackage -AllUsers | Measure-Object +$RemainingProvisioned = Get-AppxProvisionedPackage -Online | Measure-Object + +Write-LogMessage "Remaining AppX packages: $($RemainingPackages.Count)" +Write-LogMessage "Remaining provisioned packages: $($RemainingProvisioned.Count)" + +$EndTime = Get-Date +$Duration = $EndTime - $StartTime +Write-LogMessage "=== AppX Cleanup Script Completed: $EndTime ===" +Write-LogMessage "Total execution time: $($Duration.TotalMinutes.ToString('F2')) minutes" + +# Display summary +Write-Host "`n=== CLEANUP SUMMARY ===" -ForegroundColor Green +Write-Host "Log file: $LogPath" -ForegroundColor Yellow +Write-Host "Remaining packages: $($RemainingPackages.Count)" -ForegroundColor Cyan +Write-Host "Remaining provisioned: $($RemainingProvisioned.Count)" -ForegroundColor Cyan +Write-Host "Ready for sysprep!" -ForegroundColor Green From c2faafb8f7010c65614c893cef619e9c7b10ad02 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:54:52 -0400 Subject: [PATCH 087/107] Rename Cleanup-Apps4Sysprep.ps1 to Enhanced-AppX-Cleanup.ps1 --- Cleanup-Apps4Sysprep.ps1 => Enhanced-AppX-Cleanup.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Cleanup-Apps4Sysprep.ps1 => Enhanced-AppX-Cleanup.ps1 (100%) diff --git a/Cleanup-Apps4Sysprep.ps1 b/Enhanced-AppX-Cleanup.ps1 similarity index 100% rename from Cleanup-Apps4Sysprep.ps1 rename to Enhanced-AppX-Cleanup.ps1 From 08d25c7487c3dcabeac42b758ab4ab9bf6db2a60 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:57:19 -0400 Subject: [PATCH 088/107] Create Nuclear-Appx-Cleanup.ps1 --- Nuclear-Appx-Cleanup.ps1 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Nuclear-Appx-Cleanup.ps1 diff --git a/Nuclear-Appx-Cleanup.ps1 b/Nuclear-Appx-Cleanup.ps1 new file mode 100644 index 0000000..642d6b4 --- /dev/null +++ b/Nuclear-Appx-Cleanup.ps1 @@ -0,0 +1,3 @@ +# Nuclear AppX cleanup - removes EVERYTHING except core Windows Store +Get-AppxPackage -AllUsers | Where-Object {$_.Name -ne "Microsoft.WindowsStore"} | Remove-AppxPackage -ErrorAction SilentlyContinue +Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -ne "Microsoft.WindowsStore"} | Remove-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue From e8cb8cf63f3c9a4c0e4d2eb4e819443cad4aa588 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:12:52 -0400 Subject: [PATCH 089/107] Create WindowsUpdate-Reset.ps1 This script performs the following actions to reset Windows Update when a Feature Update install has failed: - Stops Windows Update services and related services - Deletes Windows Update cache folders (SoftwareDistribution and catroot2) - Clears pending update entries in the registry - Resets Windows Update component permissions - Re-registers Windows Update DLLs - Resets Windows Update policies and WinSock - Clears SCCM/Intune client cache if present - Restarts Windows Update services - Forces a new Windows Update detection cycle After running this script, a system restart is recommended before attempting to manually install the feature update again. This should clear most stuck or failed update scenarios and allow for a clean installation attempt. --- WindowsUpdate-Reset.ps1 | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 WindowsUpdate-Reset.ps1 diff --git a/WindowsUpdate-Reset.ps1 b/WindowsUpdate-Reset.ps1 new file mode 100644 index 0000000..a8202be --- /dev/null +++ b/WindowsUpdate-Reset.ps1 @@ -0,0 +1,63 @@ +# Windows Update Reset Script +# Use when Feature Updates are failing to install through SCCM or Intune + +# Run with Administrative privileges +# Stop Windows Update related services +Write-Host "Stopping Windows Update related services..." -ForegroundColor Yellow +Stop-Service -Name wuauserv, cryptSvc, bits, msiserver -Force + +# Delete Windows Update cache folders +Write-Host "Cleaning Windows Update cache..." -ForegroundColor Yellow +Get-Item "$env:SystemRoot\SoftwareDistribution*" -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue +Get-Item "$env:SystemRoot\System32\catroot2*" -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + +# Clear pending update entries in registry +Write-Host "Clearing pending update entries in registry..." -ForegroundColor Yellow +if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { + Remove-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -Force -ErrorAction SilentlyContinue +} + +# Reset Windows Update components +Write-Host "Resetting Windows Update components..." -ForegroundColor Yellow +cmd /c "sc.exe sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)" +cmd /c "sc.exe sdset wuauserv D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)" + +# Re-register DLL files +Write-Host "Re-registering Windows Update DLLs..." -ForegroundColor Yellow +$dllFiles = @( + "$env:SystemRoot\System32\wuaueng.dll", + "$env:SystemRoot\System32\wuapi.dll", + "$env:SystemRoot\System32\wups.dll", + "$env:SystemRoot\System32\wups2.dll" +) + +foreach ($dll in $dllFiles) { + if (Test-Path $dll) { + cmd /c "regsvr32.exe /s $dll" + } +} + +# Reset Windows Update policies +Write-Host "Resetting Windows Update policies..." -ForegroundColor Yellow +cmd /c "netsh winhttp reset proxy" +cmd /c "netsh winsock reset" + +# Clear SCCM/Intune client cache if client exists +if (Test-Path "$env:WinDir\CCM\CcmExec.exe") { + Write-Host "Clearing SCCM client cache..." -ForegroundColor Yellow + cmd /c "$env:WinDir\CCM\CcmExec.exe /ClearCache" +} + +# Restart Windows Update related services +Write-Host "Restarting Windows Update related services..." -ForegroundColor Yellow +Start-Service -Name bits +Start-Service -Name cryptSvc +Start-Service -Name wuauserv +Start-Service -Name msiserver + +# Force Windows Update detection cycle +Write-Host "Forcing Windows Update detection cycle..." -ForegroundColor Yellow +cmd /c "wuauclt /detectnow" +cmd /c "wuauclt /reportnow" + +Write-Host "Windows Update reset completed. The system may need to be rebooted before attempting the feature update again." -ForegroundColor Green From 7b2071325759d8786e59bf56d655626ae4f085f2 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:54:29 -0400 Subject: [PATCH 090/107] Create Find-MissingKeyInBitlocker.ps1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This script will help you identify the problematic devices. Here's what it does: Key Features: Queries all Windows managed devices in Intune Checks for BitLocker recovery keys in Azure AD for each device Identifies devices that report as encrypted but have no escrowed keys Exports results to CSV for further analysis Common Causes of Missing Keys: Windows 10 version too old - Builds before 1809 had issues with automatic key escrow BitLocker enabled before Intune enrollment - Keys won't escrow retroactively Insufficient permissions - Device needs proper Azure AD registration Group Policy conflicts - On-prem GPO overriding Intune policy TPM ownership issues - TPM not properly initialized Remediation Options: For devices missing keys, you can: powershell# Force key rotation (run on affected device with admin rights) BackupToAAD-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId (Get-BitLockerVolume -MountPoint "C:").KeyProtector[0].KeyProtectorId Or use Intune to trigger a key rotation: Device Actions → Rotate BitLocker Keys (requires Windows 10 1909+) Prerequisites: Microsoft.Graph.Authentication module Microsoft.Graph.DeviceManagement module Microsoft.Graph.Identity.DirectoryManagement module Intune Administrator or Global Administrator role --- Find-MissingKeyInBitlocker.ps1 | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Find-MissingKeyInBitlocker.ps1 diff --git a/Find-MissingKeyInBitlocker.ps1 b/Find-MissingKeyInBitlocker.ps1 new file mode 100644 index 0000000..60852b7 --- /dev/null +++ b/Find-MissingKeyInBitlocker.ps1 @@ -0,0 +1,88 @@ +# BitLocker Recovery Key Escrow Audit Script +# Identifies devices reporting as encrypted but missing escrowed recovery keys + +#Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.DeviceManagement, Microsoft.Graph.Identity.DirectoryManagement + +# Connect to Microsoft Graph with required permissions +Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan +Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All", "BitLockerKey.Read.All", "Device.Read.All" + +# Get all managed devices +Write-Host "Retrieving managed devices from Intune..." -ForegroundColor Cyan +$managedDevices = Get-MgDeviceManagementManagedDevice -All | Where-Object { $_.OperatingSystem -eq "Windows" } + +Write-Host "Found $($managedDevices.Count) Windows devices. Checking BitLocker status..." -ForegroundColor Green + +# Initialize results array +$results = @() + +foreach ($device in $managedDevices) { + Write-Host "Checking device: $($device.DeviceName)" -ForegroundColor Yellow + + # Get the Azure AD device ID + $azureAdDeviceId = $device.AzureAdDeviceId + + if ($azureAdDeviceId) { + # Check for BitLocker recovery keys + try { + $uri = "https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$azureAdDeviceId'" + $recoveryKeys = Invoke-MgGraphRequest -Uri $uri -Method GET + + $keyCount = $recoveryKeys.value.Count + + # Get encryption state from Intune + $encryptionState = $device.IsEncrypted + + # Create result object + $deviceInfo = [PSCustomObject]@{ + DeviceName = $device.DeviceName + UserPrincipalName = $device.UserPrincipalName + AzureAdDeviceId = $azureAdDeviceId + IntuneDeviceId = $device.Id + OperatingSystem = $device.OperatingSystem + OSVersion = $device.OsVersion + IsEncrypted = $encryptionState + RecoveryKeysCount = $keyCount + HasRecoveryKey = ($keyCount -gt 0) + ComplianceState = $device.ComplianceState + LastSyncDateTime = $device.LastSyncDateTime + Status = if ($encryptionState -and $keyCount -eq 0) { "MISSING KEYS" } + elseif ($encryptionState -and $keyCount -gt 0) { "OK" } + elseif (-not $encryptionState) { "NOT ENCRYPTED" } + else { "UNKNOWN" } + } + + $results += $deviceInfo + + } catch { + Write-Warning "Error checking device $($device.DeviceName): $($_.Exception.Message)" + } + } +} + +# Display summary +Write-Host "`n=== SUMMARY ===" -ForegroundColor Cyan +$missingKeys = $results | Where-Object { $_.Status -eq "MISSING KEYS" } +$encrypted = $results | Where-Object { $_.IsEncrypted -eq $true } +$withKeys = $results | Where-Object { $_.HasRecoveryKey -eq $true } + +Write-Host "Total devices checked: $($results.Count)" -ForegroundColor White +Write-Host "Encrypted devices: $($encrypted.Count)" -ForegroundColor Green +Write-Host "Devices with escrowed keys: $($withKeys.Count)" -ForegroundColor Green +Write-Host "Encrypted devices MISSING keys: $($missingKeys.Count)" -ForegroundColor Red + +# Display devices missing keys +if ($missingKeys.Count -gt 0) { + Write-Host "`n=== DEVICES ENCRYPTED BUT MISSING RECOVERY KEYS ===" -ForegroundColor Red + $missingKeys | Format-Table DeviceName, UserPrincipalName, OSVersion, LastSyncDateTime -AutoSize +} + +# Export results +$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$exportPath = ".\BitLocker_Audit_$timestamp.csv" +$results | Export-Csv -Path $exportPath -NoTypeInformation +Write-Host "`nFull results exported to: $exportPath" -ForegroundColor Green + +# Disconnect +Disconnect-MgGraph +Write-Host "`nScript completed!" -ForegroundColor Cyan From ac6d627c51732a85338a6307455229325449bda2 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:32:16 -0400 Subject: [PATCH 091/107] Create Sync-OUToSecurityGroups-Scheduled.ps1 --- Sync-OUToSecurityGroups-Scheduled.ps1 | 462 ++++++++++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 Sync-OUToSecurityGroups-Scheduled.ps1 diff --git a/Sync-OUToSecurityGroups-Scheduled.ps1 b/Sync-OUToSecurityGroups-Scheduled.ps1 new file mode 100644 index 0000000..56295ec --- /dev/null +++ b/Sync-OUToSecurityGroups-Scheduled.ps1 @@ -0,0 +1,462 @@ +<# +.SYNOPSIS + Scheduled task to automatically assign ALL users to security groups based on OU membership. + +.DESCRIPTION + This script runs on a schedule (e.g., daily) and processes all users in Active Directory, + assigning them to the appropriate security group based on their OU location. + Groups are prefixed with "SG-" to indicate they are derived from OU membership. + + Version 2.0 adds a CLEANUP PHASE to remove orphaned group memberships: + - Removes disabled users from security groups + - Removes users no longer in any mapped OU + - Removes users from incorrect groups + + Version 2.1 changes OU matching logic: + - Only checks immediate parent OU (no longer walks up DN tree) + - Allows root OU to be mapped without affecting sub-OU users + +.NOTES + Author: HK + Date: 2025-10-24 + Version: 2.1 (Fixed OU matching to check immediate parent only) + + Requirements: + - ActiveDirectory PowerShell module + - Domain-joined computer + - Service account with permissions to read AD users and modify group membership + - Scheduled Task configured to run this script + - PowerShell 5.1+ (Windows PowerShell compatible) + +.EXAMPLE + .\Sync-OUToSecurityGroups-Scheduled.ps1 + + Processes all users and assigns them to appropriate security groups, then cleans up orphaned memberships. + +.EXAMPLE + .\Sync-OUToSecurityGroups-Scheduled.ps1 -WhatIf + + Shows what changes would be made without actually making them. + +.EXAMPLE + .\Sync-OUToSecurityGroups-Scheduled.ps1 -SearchBase "OU=Users,DC=contoso,DC=com" + + Only processes users within the specified OU. + +.EXAMPLE + .\Sync-OUToSecurityGroups-Scheduled.ps1 -SkipCleanup + + Runs sync but skips cleanup phase (faster, but may leave orphaned memberships). +#> + +[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] +param( + [Parameter(Mandatory=$false)] + [string]$LogPath = "C:\Windows\Temp\OUToSecurityGroupsSync.log", + + [Parameter(Mandatory=$false)] + [string]$SearchBase = "OU=Users,DC=contoso,DC=com", + + [Parameter(Mandatory=$false)] + [string]$SecurityGroupsOU = "", + + [Parameter(Mandatory=$false)] + [switch]$SkipCleanup +) + +# Function to write to log file +function Write-Log { + param( + [string]$Message, + [ValidateSet("INFO","WARNING","ERROR","DEBUG")] + [string]$Level = "INFO" + ) + $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $LogMessage = "[$Timestamp] [$Level] $Message" + + # Ensure log directory exists + $logDir = Split-Path -Path $LogPath -Parent + if (-not (Test-Path $logDir)) { + New-Item -ItemType Directory -Path $logDir -Force | Out-Null + } + + Add-Content -Path $LogPath -Value $LogMessage + switch ($Level) { + "ERROR" { Write-Error $Message } + "WARNING" { Write-Warning $Message } + "DEBUG" { Write-Verbose $Message } + default { Write-Verbose $Message } + } +} + +# Start logging +Write-Log "==========================================" +Write-Log "OU to Security Groups Sync - SCHEDULED TASK v2.1" +Write-Log "==========================================" +Write-Log "Script started" +Write-Log "Search Base: $SearchBase" +Write-Log "Cleanup Phase: $(-not $SkipCleanup)" +Write-Log ("WhatIf Mode: {0}" -f ([bool]$WhatIfPreference)) + +# Statistics tracking +$Stats = @{ + TotalUsers = 0 + ProcessedUsers = 0 + UsersAdded = 0 + UsersRemoved = 0 + UsersSkipped = 0 + OrphanedRemoved = 0 + Errors = 0 +} + +try { + # Import Active Directory module + Import-Module ActiveDirectory -ErrorAction Stop + Write-Log "Active Directory module imported successfully" + + # --- Resolve & validate Security Groups OU (PowerShell 5.1 compatible) --- + # FIXED: Replaced ?? operator with standard PowerShell 5.1 syntax + if (-not $SecurityGroupsOU) { + $SecurityGroupsOU = "OU=Security Groups,OU=Groups,$((Get-ADDomain).DistinguishedName)" + Write-Log "SecurityGroupsOU not provided; defaulting to $SecurityGroupsOU" + } else { + $SecurityGroupsOU = $SecurityGroupsOU.Trim() + } + + try { + $securityOuObj = Get-ADOrganizationalUnit -Identity $SecurityGroupsOU -ErrorAction Stop + $securityOuDN = $securityOuObj.DistinguishedName + Write-Log "Security Groups OU resolved: $securityOuDN" + } catch { + $msg = "Security Groups OU not found: '$SecurityGroupsOU'. It must be an OU DN (e.g., OU=Security Groups,OU=Groups,DC=contoso,DC=com)" + Write-Log $msg "ERROR" + throw $msg + } + + # Define OU to Group mapping (keys are OU names) + $OUGroupMap = @{ + "Finance" = "SG-Finance" + "IT" = "SG-IT" + "HR" = "SG-HR" + "Sales" = "SG-Sales" + "Marketing" = "SG-Marketing" + "Test OU" = "SG-Test_OU" + "Executives" = "SG-Executives" + "Contractors" = "SG-Contractors" + "Support" = "SG-Support" + "Development" = "SG-Development" + } + + Write-Log "OU to Group mappings loaded: $($OUGroupMap.Count) mappings" + + # Get all enabled users + Write-Log "Retrieving all enabled users from: $SearchBase" + $Users = Get-ADUser -Filter { Enabled -eq $true } ` + -SearchBase $SearchBase ` + -Properties DistinguishedName, MemberOf, SamAccountName ` + -ResultPageSize 2000 ` + -ErrorAction Stop + + $Stats.TotalUsers = $Users.Count + Write-Log "Found $($Stats.TotalUsers) enabled users to process" + + # Check if no users were found + if ($Stats.TotalUsers -eq 0) { + Write-Log "No enabled users found in SearchBase: $SearchBase" "WARNING" + } + + # Ensure Security Groups OU exists (create if missing) + try { + $null = Get-ADOrganizationalUnit -Identity $SecurityGroupsOU -ErrorAction Stop + Write-Log "Security Groups OU exists: $SecurityGroupsOU" + } catch { + Write-Log "Security Groups OU not found: $SecurityGroupsOU" "WARNING" + # Attempt to derive parent path & name from provided DN + if ($SecurityGroupsOU -match '^OU=([^,]+),(.*)$') { + $ouName = $Matches[1] + $parentPath = $Matches[2] + $action = "Create OU '$ouName' at '$parentPath'" + if ($PSCmdlet.ShouldProcess($SecurityGroupsOU, $action)) { + New-ADOrganizationalUnit -Name $ouName ` + -Path $parentPath ` + -Description "Security groups for OU-based access policies" ` + -ProtectedFromAccidentalDeletion $true ` + -ErrorAction Stop + Write-Log "Security Groups OU created successfully" + } else { + Write-Log "Skipped: $action (WhatIf/Confirm)" "WARNING" + } + } else { + $msg = "Unable to parse SecurityGroupsOU DN '$SecurityGroupsOU' for creation. It must be an OU DN (e.g., OU=Security Groups,OU=Groups,DC=contoso,DC=com)" + Write-Log $msg "ERROR" + throw $msg + } + } + + # Pre-create all groups if they don't exist + Write-Log "Ensuring all security groups exist under $securityOuDN ..." + $AllSecurityGroups = $OUGroupMap.Values | Select-Object -Unique + + foreach ($GroupName in $AllSecurityGroups) { + try { + # Search for group anywhere in domain with proper quote escaping + $escaped = $GroupName -replace "'", "''" + $existing = Get-ADGroup -Filter "Name -eq '$escaped'" -ErrorAction SilentlyContinue + + # Alternative: Scoped search + # $existing = Get-ADGroup -LDAPFilter "(name=$escaped)" -SearchBase $securityOuDN -SearchScope Subtree -ErrorAction SilentlyContinue + + if (-not $existing) { + $action = "Create security group '$GroupName' in '$securityOuDN'" + Write-Log $action + if ($PSCmdlet.ShouldProcess($GroupName, $action)) { + New-ADGroup -Name $GroupName -Path $securityOuDN ` + -GroupScope Global -GroupCategory Security ` + -Description "OU-based security group (Auto-managed by scheduled task)" ` + -ErrorAction Stop + Write-Log "Group created: $GroupName" + } else { + Write-Log "Skipped: $action (WhatIf/Confirm)" "WARNING" + } + } else { + Write-Log "Group already exists: $GroupName at $($existing.DistinguishedName)" + } + } catch { + Write-Log "Error creating or checking group '$GroupName' : $($_.Exception.Message)" "ERROR" + $Stats.Errors++ + } + } + + # ======================================== + # PHASE 1: PROCESS USERS (Add to groups) + # ======================================== + Write-Log "==========================================" + Write-Log "PHASE 1: Processing users for group assignment" + Write-Log "==========================================" + + foreach ($User in $Users) { + $Stats.ProcessedUsers++ + try { + $DN = $User.DistinguishedName + + # Check ONLY the immediate parent OU (don't recurse up the tree) + $PrimaryOU = $null + $dnParts = $DN -split ',' + foreach ($part in $dnParts) { + if ($part -like 'OU=*') { + $ouName = $part.Substring(3) # drop 'OU=' + # Only check immediate parent OU, don't walk up the tree + if ($OUGroupMap.ContainsKey($ouName)) { + $PrimaryOU = $ouName + } + # Always break after first OU check + break + } + } + + if (-not $PrimaryOU) { + Write-Log "User $($User.SamAccountName) not in any mapped OU (DN: $DN)" "DEBUG" + $Stats.UsersSkipped++ + continue + } + + # Determine target group + $TargetGroup = $OUGroupMap[$PrimaryOU] + if (-not $TargetGroup) { + Write-Log "User $($User.SamAccountName) in unmapped OU: $PrimaryOU" "WARNING" + $Stats.UsersSkipped++ + continue + } + + # Get target group DN (ensure exists) + try { + $TargetGroupObj = Get-ADGroup -Identity $TargetGroup -ErrorAction Stop + } catch { + Write-Log "Target group '$TargetGroup' not found for user $($User.SamAccountName). Group may have failed to create." "ERROR" + $Stats.Errors++ + continue + } + + # Normalize memberOf to array + $memberOf = @() + if ($null -ne $User.MemberOf) { $memberOf = @($User.MemberOf) } + + # Add to target group if not already a member + if (-not ($memberOf -contains $TargetGroupObj.DistinguishedName)) { + $action = "Add $($User.SamAccountName) to '$TargetGroup'" + Write-Log $action + if ($PSCmdlet.ShouldProcess($TargetGroup, $action)) { + Add-ADGroupMember -Identity $TargetGroup -Members $User -ErrorAction Stop + $Stats.UsersAdded++ # Only increment if action was taken + } else { + Write-Log "Skipped: $action (WhatIf/Confirm)" "WARNING" + } + } + + # Remove from other security groups + $OtherSecurityGroups = $AllSecurityGroups | Where-Object { $_ -ne $TargetGroup } + foreach ($OtherGroup in $OtherSecurityGroups) { + try { + $OtherGroupObj = Get-ADGroup -Identity $OtherGroup -ErrorAction SilentlyContinue + if ($OtherGroupObj -and ($memberOf -contains $OtherGroupObj.DistinguishedName)) { + $action = "Remove $($User.SamAccountName) from '$OtherGroup'" + Write-Log $action + if ($PSCmdlet.ShouldProcess($OtherGroup, $action)) { + Remove-ADGroupMember -Identity $OtherGroup -Members $User -Confirm:$false -ErrorAction Stop + $Stats.UsersRemoved++ # Only increment if action was taken + } else { + Write-Log "Skipped: $action (WhatIf/Confirm)" "WARNING" + } + } + } catch { + # Group might not exist; log at DEBUG level + Write-Log "Lookup issue for group '$OtherGroup': $($_.Exception.Message)" "DEBUG" + } + } + + } catch { + Write-Log "Error processing user $($User.SamAccountName): $($_.Exception.Message)" "ERROR" + $Stats.Errors++ + } + + # Progress indicator every 50 users + if ($Stats.ProcessedUsers % 50 -eq 0) { + Write-Log "Progress: $($Stats.ProcessedUsers) / $($Stats.TotalUsers) users processed" + } + } + + Write-Log "Phase 1 completed: $($Stats.ProcessedUsers) users processed" + + # ======================================== + # PHASE 2: CLEANUP ORPHANED MEMBERSHIPS + # ======================================== + + if (-not $SkipCleanup) { + Write-Log "==========================================" + Write-Log "PHASE 2: Cleanup - Removing orphaned memberships" + Write-Log "==========================================" + + foreach ($GroupName in $AllSecurityGroups) { + try { + $Group = Get-ADGroup -Identity $GroupName -ErrorAction SilentlyContinue + if (-not $Group) { + Write-Log "Group '$GroupName' not found, skipping cleanup" "WARNING" + continue + } + + # Get all current members of this security group - ONLY USER OBJECTS + $GroupMembers = Get-ADGroupMember -Identity $GroupName -ErrorAction SilentlyContinue | + Where-Object { $_.objectClass -eq 'user' } + + if (-not $GroupMembers) { + Write-Log "Group '$GroupName' has no user members, skipping" + continue + } + + Write-Log "Checking $($GroupMembers.Count) user members in group '$GroupName'" + + foreach ($Member in $GroupMembers) { + try { + # Get member's current OU and status - use DistinguishedName for clarity + $MemberUser = Get-ADUser -Identity $Member.DistinguishedName ` + -Properties DistinguishedName, Enabled -ErrorAction SilentlyContinue + + if (-not $MemberUser) { + Write-Log "Member '$($Member.SamAccountName)' not found in AD, removing from '$GroupName'" "WARNING" + if ($PSCmdlet.ShouldProcess($GroupName, "Remove deleted user $($Member.SamAccountName)")) { + Remove-ADGroupMember -Identity $GroupName -Members $Member -Confirm:$false -ErrorAction Stop + $Stats.OrphanedRemoved++ + } + continue + } + + # Check if user is disabled + if (-not $MemberUser.Enabled) { + Write-Log "User '$($MemberUser.SamAccountName)' is disabled, removing from '$GroupName'" + if ($PSCmdlet.ShouldProcess($GroupName, "Remove disabled user $($MemberUser.SamAccountName)")) { + Remove-ADGroupMember -Identity $GroupName -Members $Member -Confirm:$false -ErrorAction Stop + $Stats.OrphanedRemoved++ + } + continue + } + + # Check if user is still in a mapped OU (immediate parent only) + $DN = $MemberUser.DistinguishedName + $dnParts = $DN -split ',' + $UserPrimaryOU = $null + + foreach ($part in $dnParts) { + if ($part -like 'OU=*') { + $ouName = $part.Substring(3) + # Only check immediate parent OU + if ($OUGroupMap.ContainsKey($ouName)) { + $UserPrimaryOU = $ouName + } + # Always break after first OU check + break + } + } + + # If user not in any mapped OU, remove from group + if (-not $UserPrimaryOU) { + Write-Log "User '$($MemberUser.SamAccountName)' not in any mapped OU, removing from '$GroupName'" + if ($PSCmdlet.ShouldProcess($GroupName, "Remove unmapped user $($MemberUser.SamAccountName)")) { + Remove-ADGroupMember -Identity $GroupName -Members $Member -Confirm:$false -ErrorAction Stop + $Stats.OrphanedRemoved++ + } + continue + } + + # If user should be in a different group, remove from this one + $CorrectGroup = $OUGroupMap[$UserPrimaryOU] + if ($CorrectGroup -ne $GroupName) { + Write-Log "User '$($MemberUser.SamAccountName)' should be in '$CorrectGroup', not '$GroupName', removing" + if ($PSCmdlet.ShouldProcess($GroupName, "Remove misplaced user $($MemberUser.SamAccountName)")) { + Remove-ADGroupMember -Identity $GroupName -Members $Member -Confirm:$false -ErrorAction Stop + $Stats.OrphanedRemoved++ + } + } + + } catch { + Write-Log "Error checking member $($Member.SamAccountName) in group '$GroupName': $($_.Exception.Message)" "ERROR" + $Stats.Errors++ + } + } + + } catch { + Write-Log "Error during cleanup for group '$GroupName': $($_.Exception.Message)" "ERROR" + $Stats.Errors++ + } + } + + Write-Log "Phase 2 completed: $($Stats.OrphanedRemoved) orphaned memberships removed" + } else { + Write-Log "Cleanup phase skipped (-SkipCleanup parameter used)" + } + + # Final summary + Write-Log "==========================================" + Write-Log "SYNC COMPLETED" + Write-Log "==========================================" + Write-Log "Total Users Found: $($Stats.TotalUsers)" + Write-Log "Users Processed: $($Stats.ProcessedUsers)" + Write-Log "Users Added to Groups: $($Stats.UsersAdded)" + Write-Log "Users Removed from Groups: $($Stats.UsersRemoved)" + Write-Log "Users Skipped: $($Stats.UsersSkipped)" + Write-Log "Orphaned Memberships Removed: $($Stats.OrphanedRemoved)" + Write-Log "Errors: $($Stats.Errors)" + Write-Log "==========================================" + + if ($Stats.Errors -gt 0) { + Write-Log "Script completed with errors" "WARNING" + exit 1 + } else { + Write-Log "Script completed successfully" + exit 0 + } + +} catch { + Write-Log "FATAL ERROR: $($_.Exception.Message)" "ERROR" + Write-Log "Stack Trace: $($_.ScriptStackTrace)" "ERROR" + exit 1 +} From 3cdf43758b43f33354aa814472b5cc4dc7544ab3 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:12:46 -0500 Subject: [PATCH 092/107] Create Intune-AutoEnrollment-HybridJoined.ps1 --- Intune-AutoEnrollment-HybridJoined.ps1 | 374 +++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 Intune-AutoEnrollment-HybridJoined.ps1 diff --git a/Intune-AutoEnrollment-HybridJoined.ps1 b/Intune-AutoEnrollment-HybridJoined.ps1 new file mode 100644 index 0000000..aacb40b --- /dev/null +++ b/Intune-AutoEnrollment-HybridJoined.ps1 @@ -0,0 +1,374 @@ +<# +.SYNOPSIS + Automated Intune enrollment script for Hybrid Azure AD joined devices +.DESCRIPTION + This script checks if a device is Hybrid Azure AD joined (both domain-joined and Azure AD joined) + and triggers Intune MDM enrollment. Adapted from Entra-joined enrollment script. + It includes robust error handling, diagnostics, and logging features. +.NOTES + Version: 1.0 + Created: December 12, 2025 + Based on: Intune-AutoEnrollment.ps1 (Entra-joined version) + Target: Hybrid Azure AD joined devices +#> + +# Script Configuration +$VerbosePreference = 'Continue' +$LogPath = "$env:ProgramData\IntuneEnrollment" +$LogFile = "$LogPath\HybridEnrollmentLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + +# Ensure log directory exists +if (-not (Test-Path $LogPath)) { + New-Item -Path $LogPath -ItemType Directory -Force | Out-Null +} + +# Logging Function +function Write-Log { + param([string]$Message) + + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logEntry = "[$timestamp] $Message" + + Write-Verbose $logEntry + Add-Content -Path $LogFile -Value $logEntry +} + +# Enhanced Device Information Collection +function Get-DeviceDetails { + $computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem + $bios = Get-CimInstance -ClassName Win32_BIOS + + $props = @{ + ComputerName = $computerSystem.Name + Manufacturer = $computerSystem.Manufacturer + Model = $computerSystem.Model + SerialNumber = $bios.SerialNumber + EnrollmentStatus = "Unknown" + JoinType = "Unknown" + } + return New-Object -TypeName PSObject -Property $props +} + +# Interactive User Detection +function Get-LoggedOnUserUpn { + try { + $sid = (Get-Process -IncludeUserName -ErrorAction SilentlyContinue | + Where-Object { $_.SessionId -gt 0 } | Select-Object -First 1).UserName + if ($sid) { return $sid } + } catch {} + return $null +} + +# Parse dsregcmd output for specific value +function Get-DsRegValue { + param( + [string[]]$DsRegOutput, + [string]$Key + ) + $line = $DsRegOutput | Where-Object { $_ -match "^\s*$Key\s*:" } + if ($line) { + return ($line -split ":\s*", 2)[1].Trim() + } + return $null +} + +# DeviceEnroller With Retry Logic +function Invoke-DeviceEnroller { + param([int]$attempt=1) + $enroller = "$env:WINDIR\System32\deviceenroller.exe" + if (Test-Path $enroller) { + Write-Log "Invoking DeviceEnroller (attempt $attempt)..." + $p = Start-Process -FilePath $enroller -ArgumentList "/c /AutoEnrollMDM" -PassThru -WindowStyle Hidden -Wait + return $p.ExitCode + } + return 2 # DeviceEnroller not found +} + +# Main Script Logic +Write-Log "==========================================" +Write-Log "Starting Hybrid Azure AD Intune enrollment script..." +Write-Log "==========================================" + +$deviceDetails = Get-DeviceDetails +Write-Log "Computer: $($deviceDetails.ComputerName)" +Write-Log "Serial: $($deviceDetails.SerialNumber)" + +# Get dsregcmd output +try { + $dsregCmd = dsregcmd /status +} catch { + Write-Log "ERROR: Failed to run dsregcmd: $_" + exit 1 +} + +# Parse join status +$azureAdJoined = Get-DsRegValue -DsRegOutput $dsregCmd -Key "AzureAdJoined" +$domainJoined = Get-DsRegValue -DsRegOutput $dsregCmd -Key "DomainJoined" +$tenantId = Get-DsRegValue -DsRegOutput $dsregCmd -Key "TenantId" +$deviceId = Get-DsRegValue -DsRegOutput $dsregCmd -Key "DeviceId" +$tenantName = Get-DsRegValue -DsRegOutput $dsregCmd -Key "TenantName" + +Write-Log "Join Status:" +Write-Log " AzureAdJoined: $azureAdJoined" +Write-Log " DomainJoined: $domainJoined" +Write-Log " TenantId: $tenantId" +Write-Log " TenantName: $tenantName" +Write-Log " DeviceId: $deviceId" + +# Check if device is Hybrid Azure AD joined +$isHybridJoined = ($azureAdJoined -eq "YES") -and ($domainJoined -eq "YES") + +if ($isHybridJoined) { + $deviceDetails.JoinType = "Hybrid Azure AD Joined" + Write-Log "Device is Hybrid Azure AD joined, proceeding with enrollment." +} elseif ($domainJoined -eq "YES" -and $azureAdJoined -ne "YES") { + $deviceDetails.JoinType = "Domain Joined Only" + Write-Log "ERROR: Device is domain-joined but NOT Azure AD joined." + Write-Log "Hybrid join may not be complete. Check Entra Connect sync and SCP configuration." + + # Additional diagnostics for hybrid join issues + $workplaceJoined = Get-DsRegValue -DsRegOutput $dsregCmd -Key "WorkplaceJoined" + Write-Log " WorkplaceJoined: $workplaceJoined" + + # Check for pending registration + $dsregCmd | Where-Object { $_ -match "ngc|KeyId|KeySignTest" } | ForEach-Object { + Write-Log " $_" + } + + exit 1 +} else { + $deviceDetails.JoinType = "Not Properly Joined" + Write-Log "ERROR: Device is not properly joined (AzureAD: $azureAdJoined, Domain: $domainJoined)" + exit 1 +} + +# Check for proxy configuration +$netsh = (netsh winhttp show proxy) 2>$null +if ($netsh -and ($netsh -match 'Proxy Server')) { + Write-Log "WinHTTP proxy in use: $($netsh -replace '\s+',' ')" +} + +# Check if already enrolled in Intune +$enrolled = $false +$alreadyEnrolled = $false +$userPresent = $false + +# Architecture-Agnostic Registry Operations +$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, + [Microsoft.Win32.RegistryView]::Registry64) +$enrollKey = $base.OpenSubKey("SOFTWARE\Microsoft\Enrollments") + +if ($enrollKey) { + foreach ($sub in $enrollKey.GetSubKeyNames()) { + if ($sub -match '^\{[0-9A-Fa-f-]+\}$') { + $subKey = $enrollKey.OpenSubKey($sub) + $prov = $subKey.GetValue('ProviderID', $null) + if ($prov -eq 'MS DM Server') { + $alreadyEnrolled = $true + $upn = $subKey.GetValue('UPN', 'Unknown') + Write-Log "Device is already enrolled in Intune (UPN: $upn)" + break + } + } + } +} + +if ($alreadyEnrolled) { + $enrolled = $true + $deviceDetails.EnrollmentStatus = "Already Enrolled" +} else { + Write-Log "Device is not currently enrolled in Intune. Attempting enrollment..." + + # Check for interactive user + $userPresent = [bool](Get-LoggedOnUserUpn) + $loggedOnUser = Get-LoggedOnUserUpn + Write-Log "Interactive user present: $userPresent" + if ($loggedOnUser) { + Write-Log "Logged on user: $loggedOnUser" + } + + if (-not $userPresent) { + Write-Log "WARNING: No interactive user detected; enrollment may defer until next sign-in." + } + + # Hardened MDM Policy Write + $mdmKey = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM" + try { + if (-not (Test-Path $mdmKey)) { + New-Item -Path $mdmKey -Force -ErrorAction Stop | Out-Null + Write-Log "Created MDM policy key" + } + + New-ItemProperty -Path $mdmKey -Name 'AutoEnrollMDM' -PropertyType DWord -Value 1 -Force -ErrorAction Stop | Out-Null + New-ItemProperty -Path $mdmKey -Name 'UseAADCredentialType' -PropertyType DWord -Value 1 -Force -ErrorAction Stop | Out-Null + + $ae = (Get-ItemProperty $mdmKey -ErrorAction Stop).AutoEnrollMDM + $ct = (Get-ItemProperty $mdmKey -ErrorAction Stop).UseAADCredentialType + + Write-Log "MDM Policy values - AutoEnrollMDM: $ae, UseAADCredentialType: $ct" + + if ($ae -ne 1 -or $ct -ne 1) { + Write-Log "ERROR: Policy write failed verification." + exit 5 + } + Write-Log "MDM policy keys configured successfully" + } catch { + Write-Log "ERROR: Policy write failed: $_" + exit 5 + } + + # Improved Scheduled Task Discovery + $emPath = "\Microsoft\Windows\EnterpriseMgmt\" + $tasks = Get-ScheduledTask -TaskPath $emPath -ErrorAction SilentlyContinue | Where-Object { + $_.TaskName -match 'enrollment|PushLaunch|Schedule' + } + + if ($tasks) { + $toStart = $tasks | Sort-Object { + try { [datetime]$_.LastRunTime } catch { [datetime]::MinValue } + } -Descending | Select-Object -First 1 + Write-Log "Found enrollment task: '$($toStart.TaskName)' - Starting..." + try { + Start-ScheduledTask -TaskPath $emPath -TaskName $toStart.TaskName -ErrorAction Stop + Write-Log "Enrollment task started successfully" + } catch { + Write-Log "WARNING: Failed to start scheduled task: $_" + } + } else { + Write-Log "No enrollment scheduled tasks found. Using DeviceEnroller.exe..." + + # Fall back to DeviceEnroller.exe + $enrollerExitCode = Invoke-DeviceEnroller -attempt 1 + Write-Log "DeviceEnroller attempt 1 exit code: $enrollerExitCode" + + if ($enrollerExitCode -ne 0) { + Write-Log "First DeviceEnroller attempt returned $enrollerExitCode, retrying after delay..." + Start-Sleep 10 + $enrollerExitCode = Invoke-DeviceEnroller -attempt 2 + Write-Log "DeviceEnroller attempt 2 exit code: $enrollerExitCode" + } + } + + # Wait for enrollment to process + Write-Log "Waiting 30 seconds for enrollment to process..." + Start-Sleep -Seconds 30 + + # Comprehensive Event Log Checking + try { + $dmLog = "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin" + $recentIds = 75, 76, 201, 202, 205, 207, 208, 209, 301, 305 + $events = Get-WinEvent -LogName $dmLog -MaxEvents 300 -ErrorAction SilentlyContinue | + Where-Object { $_.Id -in $recentIds -and $_.TimeCreated -gt (Get-Date).AddHours(-1) } + + if ($events -and $events.Count -gt 0) { + Write-Log "Found $($events.Count) recent MDM events indicating enrollment activity:" + $events | Select-Object -First 5 | ForEach-Object { + Write-Log " Event $($_.Id): $($_.Message.Substring(0, [Math]::Min(100, $_.Message.Length)))..." + } + $enrolled = $true + } else { + Write-Log "No recent MDM enrollment events found in the last hour." + } + } catch { + Write-Log "WARNING: Error checking event logs: $_" + # Fallback with smaller query if the first one fails + try { + $events = Get-WinEvent -LogName $dmLog -MaxEvents 50 -ErrorAction SilentlyContinue | + Where-Object { $_.Id -in $recentIds } + if ($events -and $events.Count -gt 0) { + Write-Log "Found $($events.Count) MDM events in fallback query." + $enrolled = $true + } + } catch { + Write-Log "WARNING: Fallback event query also failed: $_" + } + } + + # Double-check enrollment status in registry after waiting + $enrollKey = $base.OpenSubKey("SOFTWARE\Microsoft\Enrollments") + if ($enrollKey) { + foreach ($sub in $enrollKey.GetSubKeyNames()) { + if ($sub -match '^\{[0-9A-Fa-f-]+\}$') { + $subKey = $enrollKey.OpenSubKey($sub) + $prov = $subKey.GetValue('ProviderID', $null) + if ($prov -eq 'MS DM Server') { + $enrolled = $true + Write-Log "Confirmed: Device is now enrolled in Intune (registry check)" + break + } + } + } + } +} + +# MDM Diagnostics Collection on Failure +if (-not $enrolled) { + Write-Log "Enrollment not confirmed. Collecting MDM diagnostics..." + try { + $diagPath = "$env:ProgramData\IntuneDiagnostics" + if (-not (Test-Path $diagPath)) { New-Item -Path $diagPath -ItemType Directory -Force | Out-Null } + + $cab = Join-Path $diagPath ("MDMDiag_Hybrid_{0}.cab" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) + $diagTool = "$env:SystemRoot\System32\mdmdiagnosticstool.exe" + + if (Test-Path $diagTool) { + Start-Process -FilePath $diagTool ` + -ArgumentList "-area DeviceEnrollment;DeviceProvisioning;Autopilot -cab `"$cab`"" -Wait -WindowStyle Hidden + Write-Log "Captured MDMDiagnostics to $cab" + } else { + Write-Log "WARNING: MDMDiagnosticsTool.exe not found" + } + } catch { + Write-Log "WARNING: MDMDiagnostics collection failed: $_" + } +} + +# Improved Exit Code Handling +if ($enrolled -or $alreadyEnrolled) { + $deviceDetails.EnrollmentStatus = "Enrolled" + Write-Log "==========================================" + Write-Log "SUCCESS: Device is enrolled in Intune." + Write-Log "==========================================" + $exitCode = 0 # Success +} elseif (-not $isHybridJoined) { + $deviceDetails.EnrollmentStatus = "Not Hybrid Joined" + Write-Log "FAILED: Device is not Hybrid Azure AD joined." + $exitCode = 1 # Not properly joined +} elseif (-not (Test-Path "$env:WINDIR\System32\deviceenroller.exe")) { + $deviceDetails.EnrollmentStatus = "Missing DeviceEnroller" + Write-Log "FAILED: DeviceEnroller.exe not found." + $exitCode = 2 # DeviceEnroller missing +} elseif (-not $userPresent) { + $deviceDetails.EnrollmentStatus = "Deferred - No User" + Write-Log "DEFERRED: Enrollment deferred until user login." + $exitCode = 4 # No interactive user +} else { + $deviceDetails.EnrollmentStatus = "Pending" + Write-Log "PENDING: Enrollment attempted but not yet confirmed." + Write-Log "Device may appear in Intune within the next few minutes." + Write-Log "Check: https://intune.microsoft.com/#view/Microsoft_Intune_DeviceSettings/DevicesMenu/~/allDevices" + $exitCode = 3 # Pending +} + +Write-Log "Log file: $LogFile" + +# Output summary for ManageEngine/RMM tools +$output = @" +======================================== +HYBRID INTUNE ENROLLMENT SUMMARY +======================================== +Computer: $($deviceDetails.ComputerName) +Serial: $($deviceDetails.SerialNumber) +Join Type: $($deviceDetails.JoinType) +Enrollment Status: $($deviceDetails.EnrollmentStatus) +Tenant: $tenantName +Device ID: $deviceId +Exit Code: $exitCode +Log File: $LogFile +======================================== +"@ + +Write-Output $output +Write-Log "Script completed with exit code: $exitCode" + +exit $exitCode From bb41a4f7f8acb1b738bf29a3c13d7fde268b7bcf Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:49:33 -0500 Subject: [PATCH 093/107] Create Add-ADFSDeviceClaimRules.ps1 --- Add-ADFSDeviceClaimRules.ps1 | 219 +++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 Add-ADFSDeviceClaimRules.ps1 diff --git a/Add-ADFSDeviceClaimRules.ps1 b/Add-ADFSDeviceClaimRules.ps1 new file mode 100644 index 0000000..a649d6d --- /dev/null +++ b/Add-ADFSDeviceClaimRules.ps1 @@ -0,0 +1,219 @@ +<# +.SYNOPSIS + Adds device authentication claim rules to ADFS for hybrid Azure AD join with MDM auto-enrollment. + +.DESCRIPTION + This script adds the required device claim rules to the Microsoft Office 365 Identity Platform + relying party trust in ADFS. These rules are required for: + - Hybrid Azure AD device registration + - MDM auto-enrollment via Intune + + Without these rules, devices register as hybrid Azure AD joined but MDM URLs remain blank, + preventing Intune enrollment. + +.PARAMETER FederatedDomain + Your organization's federated domain (e.g., contoso.com). This must match a verified + federated domain in Entra ID. Check Entra ID > Custom domain names to find yours. + +.PARAMETER RelyingPartyName + The name of the ADFS relying party trust. Default: "Microsoft Office 365 Identity Platform" + +.PARAMETER WhatIf + Shows what changes would be made without applying them. + +.EXAMPLE + .\Add-ADFSDeviceClaimRules.ps1 -FederatedDomain "contoso.com" + + Adds device claim rules using contoso.com as the federated domain. + +.EXAMPLE + .\Add-ADFSDeviceClaimRules.ps1 -FederatedDomain "contoso.com" -WhatIf + + Shows what would be changed without making changes. + +.NOTES + Requirements: + - Must run on ADFS server as Administrator + - ADFS service must be running + - Federated domain must be verified in Entra ID + + References: + - https://learn.microsoft.com/en-us/azure/active-directory/devices/hybrid-azuread-join-federated-domains + - https://learn.microsoft.com/en-us/azure/active-directory/devices/hybrid-azuread-join-manual + + +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory = $true, HelpMessage = "Your federated domain (e.g., contoso.com)")] + [ValidateNotNullOrEmpty()] + [string]$FederatedDomain, + + [Parameter(Mandatory = $false)] + [string]$RelyingPartyName = "Microsoft Office 365 Identity Platform" +) + +#Requires -RunAsAdministrator + +$ErrorActionPreference = "Stop" + +Write-Host "" +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "ADFS Device Claim Rules - MDM Enrollment Fix" -ForegroundColor Cyan +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Relying Party: $RelyingPartyName" -ForegroundColor Yellow +Write-Host "Federated Domain: $FederatedDomain" -ForegroundColor Yellow +Write-Host "" + +# Validate federated domain format +if ($FederatedDomain -notmatch "^[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z]{2,}$") { + Write-Host "ERROR: Invalid domain format. Expected format: contoso.com" -ForegroundColor Red + exit 1 +} + +# Step 1: Get current relying party trust +Write-Host "[1/5] Getting current relying party trust..." -ForegroundColor Green +try { + $rp = Get-AdfsRelyingPartyTrust -Name $RelyingPartyName + if (-not $rp) { + throw "Relying party trust '$RelyingPartyName' not found. Verify ADFS is configured for Office 365." + } + Write-Host " Found relying party trust." -ForegroundColor Gray +} +catch { + Write-Host "ERROR: $_" -ForegroundColor Red + Write-Host "" + Write-Host "Troubleshooting:" -ForegroundColor Yellow + Write-Host " - Ensure you're running this on the ADFS server" -ForegroundColor Gray + Write-Host " - Verify ADFS service is running: Get-Service adfssrv" -ForegroundColor Gray + Write-Host " - List relying parties: Get-AdfsRelyingPartyTrust | Select Name" -ForegroundColor Gray + exit 1 +} + +# Step 2: Backup current rules +Write-Host "[2/5] Backing up current rules..." -ForegroundColor Green +$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' +$backupPath = ".\ADFS_ClaimRules_Backup_$timestamp.txt" +$rp.IssuanceTransformRules | Out-File -FilePath $backupPath -Encoding UTF8 +Write-Host " Backup saved to: $backupPath" -ForegroundColor Gray + +# Step 3: Check if device rules already exist +Write-Host "[3/5] Checking for existing device claim rules..." -ForegroundColor Green +$existingRules = $rp.IssuanceTransformRules + +$hasAccountType = $existingRules -match "accounttype.*DJ" +$hasObjectGuid = $existingRules -match "onpremobjectguid" +$hasPrimarySid = $existingRules -match "primarysid.*issue\(claims" +$hasIssuerId = $existingRules -match "issuerid.*adfs/services/trust" + +if ($hasAccountType -or $hasObjectGuid -or $hasPrimarySid -or $hasIssuerId) { + Write-Host " WARNING: Some device claim rules may already exist:" -ForegroundColor Yellow + if ($hasAccountType) { Write-Host " - Account type rule found" -ForegroundColor Yellow } + if ($hasObjectGuid) { Write-Host " - ObjectGUID rule found" -ForegroundColor Yellow } + if ($hasPrimarySid) { Write-Host " - PrimarySID rule found" -ForegroundColor Yellow } + if ($hasIssuerId) { Write-Host " - IssuerID rule found" -ForegroundColor Yellow } + Write-Host "" + $continue = Read-Host " Continue anyway? (y/N)" + if ($continue -ne "y") { + Write-Host " Aborted by user." -ForegroundColor Yellow + exit 0 + } +} +else { + Write-Host " No existing device rules found. Proceeding..." -ForegroundColor Gray +} + +# Step 4: Define new device claim rules +Write-Host "[4/5] Preparing device claim rules..." -ForegroundColor Green + +$deviceClaimRules = @" + +@RuleName = "Issue account type for domain-joined computers" +c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + => issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype", Value = "DJ"); + +@RuleName = "Issue objectGUID for domain-joined computers" +c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + && c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + => issue(store = "Active Directory", types = ("http://schemas.microsoft.com/identity/claims/onpremobjectguid"), query = ";objectguid;{0}", param = c2.Value); + +@RuleName = "Issue primarySID for domain-joined computers" +c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + && c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + => issue(claims = c2); + +@RuleName = "Issue issuerID for domain-joined computers" +c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] + => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/issuerid", Value = "http://$FederatedDomain/adfs/services/trust/"); + +"@ + +Write-Host " Device claim rules prepared:" -ForegroundColor Gray +Write-Host " - Account type (DJ)" -ForegroundColor Gray +Write-Host " - On-premises objectGUID" -ForegroundColor Gray +Write-Host " - Primary SID" -ForegroundColor Gray +Write-Host " - Issuer ID (http://$FederatedDomain/adfs/services/trust/)" -ForegroundColor Gray + +# Step 5: Apply new rules +Write-Host "[5/5] Applying updated claim rules..." -ForegroundColor Green + +$newRules = $existingRules + $deviceClaimRules + +if ($PSCmdlet.ShouldProcess($RelyingPartyName, "Update IssuanceTransformRules with device claims")) { + try { + Set-AdfsRelyingPartyTrust -TargetName $RelyingPartyName -IssuanceTransformRules $newRules + Write-Host " Rules applied successfully!" -ForegroundColor Green + } + catch { + Write-Host "ERROR applying rules: $_" -ForegroundColor Red + Write-Host "" + Write-Host "Attempting to restore from backup..." -ForegroundColor Yellow + try { + $backupRules = Get-Content $backupPath -Raw + Set-AdfsRelyingPartyTrust -TargetName $RelyingPartyName -IssuanceTransformRules $backupRules + Write-Host "Backup restored successfully." -ForegroundColor Green + } + catch { + Write-Host "ERROR restoring backup: $_" -ForegroundColor Red + Write-Host "Manual restore required from: $backupPath" -ForegroundColor Red + } + exit 1 + } +} + +Write-Host "" +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "COMPLETE" -ForegroundColor Green +Write-Host "============================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Next Steps:" -ForegroundColor Yellow +Write-Host "" +Write-Host " 1. Restart ADFS service (recommended):" -ForegroundColor White +Write-Host " Restart-Service adfssrv" -ForegroundColor Gray +Write-Host "" +Write-Host " 2. On a test device, re-register with Azure AD:" -ForegroundColor White +Write-Host " dsregcmd /leave" -ForegroundColor Gray +Write-Host " Start-Sleep -Seconds 30" -ForegroundColor Gray +Write-Host " dsregcmd /join" -ForegroundColor Gray +Write-Host " Restart-Computer" -ForegroundColor Gray +Write-Host "" +Write-Host " 3. After reboot, verify MDM URLs are populated:" -ForegroundColor White +Write-Host " dsregcmd /status" -ForegroundColor Gray +Write-Host "" +Write-Host " Expected output:" -ForegroundColor Gray +Write-Host " MdmUrl : https://enrollment.manage.microsoft.com/..." -ForegroundColor DarkGray +Write-Host " MdmTouUrl : https://portal.manage.microsoft.com/..." -ForegroundColor DarkGray +Write-Host " MdmComplianceUrl : https://portal.manage.microsoft.com/..." -ForegroundColor DarkGray +Write-Host "" +Write-Host " 4. Check Intune portal for device enrollment (may take 5-15 min)" -ForegroundColor White +Write-Host "" +Write-Host "Rollback:" -ForegroundColor Yellow +Write-Host " If issues occur, restore the backup:" -ForegroundColor White +Write-Host " `$backup = Get-Content '$backupPath' -Raw" -ForegroundColor Gray +Write-Host " Set-AdfsRelyingPartyTrust -TargetName '$RelyingPartyName' -IssuanceTransformRules `$backup" -ForegroundColor Gray +Write-Host " Restart-Service adfssrv" -ForegroundColor Gray +Write-Host "" +Write-Host "Backup location: $backupPath" -ForegroundColor Cyan +Write-Host "" From 4ca393ce7a2305219fc86c10a42ae55d2bf10cb0 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:13:15 -0500 Subject: [PATCH 094/107] Update Add-ADFSDeviceClaimRules.ps1 --- Add-ADFSDeviceClaimRules.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Add-ADFSDeviceClaimRules.ps1 b/Add-ADFSDeviceClaimRules.ps1 index a649d6d..c3ebd16 100644 --- a/Add-ADFSDeviceClaimRules.ps1 +++ b/Add-ADFSDeviceClaimRules.ps1 @@ -142,7 +142,7 @@ c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", @RuleName = "Issue primarySID for domain-joined computers" c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] && c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] - => issue(claims = c2); + => issue(claim = c2); @RuleName = "Issue issuerID for domain-joined computers" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] From 45ec6591ab8dda422c66253b9800abe4dee9c4e1 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:56:14 -0500 Subject: [PATCH 095/107] Update Get-GPOReports.ps1 Multiple improvements. --- Get-GPOReports.ps1 | 75 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/Get-GPOReports.ps1 b/Get-GPOReports.ps1 index 8d11412..55434bc 100644 --- a/Get-GPOReports.ps1 +++ b/Get-GPOReports.ps1 @@ -1,27 +1,68 @@ -# Get all GPOs and generate XML report for each +<# +.SYNOPSIS +Export GPO reports from Active Directory domains +.DESCRIPTION +Exports all GPOs to XML (for Group Policy Analytics) and HTML (for review) +Supports multi-domain environments +.PARAMETER Domain +Target domain FQDN. If not specified, uses current domain. +.PARAMETER OutputPath +Base output directory. Domain subfolder created automatically. +.PARAMETER ReportType +XML, HTML, or Both (default: Both) +.EXAMPLE +.\Get-GPOReports.ps1 -Domain "contoso.com" -OutputPath "C:\GPOReports" +.\Get-GPOReports.ps1 -Domain "dom.contoso.com" -OutputPath "C:\GPOReports" +#> -# Set the output path where the XML reports will be saved +param( +[string]$Domain = $env:USERDNSDOMAIN, +[string]$OutputPath = "C:\GPOReports", +[ValidateSet("XML", "HTML", "Both")] +[string]$ReportType = "Both" +) -$outputPath = "C:\GPOReports\" - -if (!(Test-Path $outputPath)) { - -New-Item -ItemType Directory -Path $outputPath - -# Get all Group Policy Objects from the domain - -$allGPOs = Get-GPO -All +# Create domain-specific subfolder +$domainFolder = Join-Path $OutputPath ($Domain -replace '\.', '_') +if (!(Test-Path $domainFolder)) { +New-Item -ItemType Directory -Path $domainFolder -Force | Out-Null +} -# Loop through each GPO and export it to an XML report +# Get all GPOs from specified domain +try { +$allGPOs = Get-GPO -All -Domain $Domain -ErrorAction Stop +Write-Host "Found $($allGPOs.Count) GPOs in $Domain" -ForegroundColor Cyan +} +catch { +Write-Error "Failed to retrieve GPOs from $Domain : $_" +exit 1 +} +$exported = 0 foreach ($gpo in $allGPOs) { +# Sanitize filename (remove invalid characters) +$safeName = $gpo.DisplayName -replace '[\\/:*?"<>|]', '_' -$gpoReport = get-gporeport $gpo.DisplayName -ReportType xml - -out-File -filepath ($outputpath + $gpo.DisplayName + ".xml") -inputobject $gpoReport +try { +if ($ReportType -in @("XML", "Both")) { +$xmlPath = Join-Path $domainFolder "$safeName.xml" +Get-GPOReport -Guid $gpo.Id -Domain $Domain -ReportType XML | +Out-File -FilePath $xmlPath -Encoding UTF8 +} -Write-Host "Exported GPO $($gpo.DisplayName)" +if ($ReportType -in @("HTML", "Both")) { +$htmlPath = Join-Path $domainFolder "$safeName.html" +Get-GPOReport -Guid $gpo.Id -Domain $Domain -ReportType HTML | +Out-File -FilePath $htmlPath -Encoding UTF8 +} +$exported++ +Write-Host "[$exported/$($allGPOs.Count)] $($gpo.DisplayName)" -ForegroundColor Green } +catch { +Write-Warning "Failed to export '$($gpo.DisplayName)': $_" +} +} + +Write-Host "`nExported $exported GPOs to $domainFolder" -ForegroundColor Cyan -} \ No newline at end of file From f57d2e0b3b8e8a763495a7d9fa3b642b87e7f405 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:08:13 -0500 Subject: [PATCH 096/107] Create Detect-MdmEnrollmentBlock.ps1 --- Detect-MdmEnrollmentBlock.ps1 | 128 ++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 Detect-MdmEnrollmentBlock.ps1 diff --git a/Detect-MdmEnrollmentBlock.ps1 b/Detect-MdmEnrollmentBlock.ps1 new file mode 100644 index 0000000..f34f700 --- /dev/null +++ b/Detect-MdmEnrollmentBlock.ps1 @@ -0,0 +1,128 @@ +<# +.SYNOPSIS + Detection-only script for MDM enrollment blockers. + +.DESCRIPTION + Lightweight assessment script to identify policy-backed registry values + that may block MDM/Intune enrollment. Makes no changes. + + Exit Codes: + 0 = No blocking values found + 1 = Blocking values detected + 2 = Error during detection + +.NOTES + Version: 1.0 + Author: HK + Creation Date: 2026-01-22 +#> + +[CmdletBinding()] +param() + +$BlockersFound = $false +$Results = @() + +Write-Host "MDM Enrollment Block Detection" -ForegroundColor Cyan +Write-Host "Computer: $env:COMPUTERNAME" +Write-Host "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" +Write-Host "=" * 60 + +# Check DisableRegistration +$MdmPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM" +if (Test-Path $MdmPath) { + $DisableReg = (Get-ItemProperty -Path $MdmPath -Name "DisableRegistration" -ErrorAction SilentlyContinue).DisableRegistration + if ($DisableReg -eq 1) { + Write-Host "[BLOCKED] DisableRegistration = 1" -ForegroundColor Red + $Results += "DisableRegistration=1 (BLOCKED)" + $BlockersFound = $true + } + elseif ($null -ne $DisableReg) { + Write-Host "[OK] DisableRegistration = $DisableReg" -ForegroundColor Green + } +} +else { + Write-Host "[OK] MDM policy key does not exist" -ForegroundColor Green +} + +# Check BlockAADWorkplaceJoin +$WpjPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WorkplaceJoin" +if (Test-Path $WpjPath) { + $BlockWpj = (Get-ItemProperty -Path $WpjPath -Name "BlockAADWorkplaceJoin" -ErrorAction SilentlyContinue).BlockAADWorkplaceJoin + if ($BlockWpj -eq 1) { + Write-Host "[BLOCKED] BlockAADWorkplaceJoin = 1" -ForegroundColor Red + $Results += "BlockAADWorkplaceJoin=1 (BLOCKED)" + $BlockersFound = $true + } + elseif ($null -ne $BlockWpj) { + Write-Host "[OK] BlockAADWorkplaceJoin = $BlockWpj" -ForegroundColor Green + } + + $AutoWpj = (Get-ItemProperty -Path $WpjPath -Name "autoWorkplaceJoin" -ErrorAction SilentlyContinue).autoWorkplaceJoin + if ($AutoWpj -eq 0) { + Write-Host "[WARN] autoWorkplaceJoin = 0 (disabled)" -ForegroundColor Yellow + $Results += "autoWorkplaceJoin=0 (disabled)" + } +} +else { + Write-Host "[OK] WorkplaceJoin policy key does not exist" -ForegroundColor Green +} + +# Check WMI MDM Authority +Write-Host "" +Write-Host "Checking WMI MDM Authority..." -ForegroundColor Cyan +try { + $MdmAuth = Get-CimInstance -Namespace "root/cimv2/mdm" -ClassName MDM_MgmtAuthority -ErrorAction Stop + if ($MdmAuth) { + Write-Host "[WARN] WMI MDM Authority record exists (possible stale enrollment)" -ForegroundColor Yellow + $Results += "WMI MDM Authority present" + } +} +catch { + Write-Host "[OK] No WMI MDM Authority (namespace not present or empty)" -ForegroundColor Green +} + +# Check for legacy scheduled tasks +Write-Host "" +Write-Host "Checking legacy scheduled tasks..." -ForegroundColor Cyan +$LegacyTasks = Get-ScheduledTask -TaskPath "\Microsoft\Windows\EnterpriseMgmt\*" -ErrorAction SilentlyContinue +if ($LegacyTasks) { + Write-Host "[WARN] Legacy EnterpriseMgmt tasks found: $($LegacyTasks.Count)" -ForegroundColor Yellow + $LegacyTasks | ForEach-Object { Write-Host " - $($_.TaskPath)$($_.TaskName)" -ForegroundColor Yellow } + $Results += "Legacy scheduled tasks: $($LegacyTasks.Count)" +} +else { + Write-Host "[OK] No legacy EnterpriseMgmt scheduled tasks" -ForegroundColor Green +} + +# Quick dsregcmd status +Write-Host "" +Write-Host "Device registration status:" -ForegroundColor Cyan +$Dsreg = dsregcmd /status 2>&1 +$AzureAdJoined = ($Dsreg | Select-String "AzureAdJoined\s*:\s*(\w+)").Matches.Groups[1].Value +$DomainJoined = ($Dsreg | Select-String "DomainJoined\s*:\s*(\w+)").Matches.Groups[1].Value +$WorkplaceJoined = ($Dsreg | Select-String "WorkplaceJoined\s*:\s*(\w+)").Matches.Groups[1].Value +$MdmUrl = ($Dsreg | Select-String "MdmUrl\s*:\s*(.+)").Matches.Groups[1].Value + +Write-Host " DomainJoined: $DomainJoined" +Write-Host " AzureAdJoined: $AzureAdJoined" +Write-Host " WorkplaceJoined: $WorkplaceJoined" +Write-Host " MdmUrl: $(if ($MdmUrl) { $MdmUrl } else { '(not enrolled)' })" + +# Summary +Write-Host "" +Write-Host "=" * 60 +if ($BlockersFound) { + Write-Host "RESULT: BLOCKERS DETECTED" -ForegroundColor Red + Write-Host "Findings:" -ForegroundColor Red + $Results | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } + exit 1 +} +else { + Write-Host "RESULT: NO BLOCKERS FOUND" -ForegroundColor Green + if ($Results.Count -gt 0) { + Write-Host "Warnings:" -ForegroundColor Yellow + $Results | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow } + } + exit 0 +} From e79a7dc17532e106c2bf15aa521a88ca9effe036 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:10:33 -0500 Subject: [PATCH 097/107] Create Remediate-MdmEnrollmentBlock.ps1 --- Remediate-MdmEnrollmentBlock.ps1 | 436 +++++++++++++++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 Remediate-MdmEnrollmentBlock.ps1 diff --git a/Remediate-MdmEnrollmentBlock.ps1 b/Remediate-MdmEnrollmentBlock.ps1 new file mode 100644 index 0000000..b9955dc --- /dev/null +++ b/Remediate-MdmEnrollmentBlock.ps1 @@ -0,0 +1,436 @@ +<# +.SYNOPSIS + Pre-Intune MDM Enrollment Remediation Script + +.DESCRIPTION + Removes policy-backed registry values that block MDM/Intune enrollment. + Designed for deployment via GPO Startup Script or ManageEngine Endpoint Central. + + Targets: + - DisableRegistration (hard block to MDM enrollment) + - BlockAADWorkplaceJoin (blocks Azure AD workplace join) + - Legacy enrollment artifacts under Policies key + +.PARAMETER DetectOnly + Run in detection/audit mode without making changes. + +.PARAMETER IncludeWmiCleanup + Also check for and remove orphaned WMI MDM authority records. + +.PARAMETER Force + Skip confirmation prompts (for unattended execution). + +.NOTES + Version: 1.0 + Author: HK + Creation Date: 2026-01-22 + Purpose: Pre-enrollment remediation for Intune + + CHANGE RECORD: + -------------- + CR#: [TBD] + Requested By: Scott Klander / Paul + Approved By: [TBD] + Implementation: GPO Startup Script / ManageEngine + Rollback: Restore from backup in C:\ProgramData\MdmEnrollmentRemediation\Backup\ + +.EXAMPLE + .\Remediate-MdmEnrollmentBlock.ps1 -DetectOnly + Audit mode - reports findings without changes. + +.EXAMPLE + .\Remediate-MdmEnrollmentBlock.ps1 -Force + Full remediation, unattended. + +.EXAMPLE + .\Remediate-MdmEnrollmentBlock.ps1 -Force -IncludeWmiCleanup + Full remediation including WMI MDM authority cleanup. +#> + +[CmdletBinding()] +param( + [switch]$DetectOnly, + [switch]$IncludeWmiCleanup, + [switch]$Force +) + +#Requires -RunAsAdministrator + +# ============================================================================ +# CONFIGURATION +# ============================================================================ + +$Script:Config = @{ + LogPath = "C:\ProgramData\MdmEnrollmentRemediation" + BackupPath = "C:\ProgramData\MdmEnrollmentRemediation\Backup" + LogFile = "Remediation_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + EventLogName = "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin" + + # Registry paths to remediate (Policy keys - GPO/MDM controlled) + RegistryTargets = @( + @{ + Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\MDM" + Values = @( + @{ Name = "DisableRegistration"; BlockingValue = 1; Description = "MDM Registration Disabled" } + ) + }, + @{ + Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WorkplaceJoin" + Values = @( + @{ Name = "BlockAADWorkplaceJoin"; BlockingValue = 1; Description = "AAD Workplace Join Blocked" } + @{ Name = "autoWorkplaceJoin"; BlockingValue = 0; Description = "Auto Workplace Join Disabled" } + ) + } + ) +} + +# ============================================================================ +# LOGGING FUNCTIONS +# ============================================================================ + +function Initialize-Logging { + if (-not (Test-Path $Script:Config.LogPath)) { + New-Item -Path $Script:Config.LogPath -ItemType Directory -Force | Out-Null + } + if (-not (Test-Path $Script:Config.BackupPath)) { + New-Item -Path $Script:Config.BackupPath -ItemType Directory -Force | Out-Null + } + + $Script:LogFilePath = Join-Path $Script:Config.LogPath $Script:Config.LogFile + Start-Transcript -Path $Script:LogFilePath -Append +} + +function Write-Log { + param( + [string]$Message, + [ValidateSet("INFO", "WARN", "ERROR", "SUCCESS", "DETECT")] + [string]$Level = "INFO" + ) + + $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $LogEntry = "[$Timestamp] [$Level] $Message" + + switch ($Level) { + "ERROR" { Write-Host $LogEntry -ForegroundColor Red } + "WARN" { Write-Host $LogEntry -ForegroundColor Yellow } + "SUCCESS" { Write-Host $LogEntry -ForegroundColor Green } + "DETECT" { Write-Host $LogEntry -ForegroundColor Cyan } + default { Write-Host $LogEntry } + } +} + +# ============================================================================ +# BACKUP FUNCTIONS +# ============================================================================ + +function Backup-RegistryKey { + param( + [string]$Path + ) + + if (-not (Test-Path $Path)) { + Write-Log "Registry path does not exist, skipping backup: $Path" -Level INFO + return $null + } + + $KeyName = ($Path -replace "HKLM:\\", "" -replace "\\", "_") + $BackupFile = Join-Path $Script:Config.BackupPath "$KeyName`_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg" + + try { + # Convert PowerShell path to reg.exe format + $RegPath = $Path -replace "HKLM:\\", "HKLM\" + $Result = reg export $RegPath $BackupFile /y 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Log "Backed up: $Path -> $BackupFile" -Level SUCCESS + return $BackupFile + } else { + Write-Log "Backup warning for $Path : $Result" -Level WARN + return $null + } + } + catch { + Write-Log "Failed to backup $Path : $_" -Level ERROR + return $null + } +} + +function Export-EventLog { + $ExportFile = Join-Path $Script:Config.BackupPath "DMEDP_Events_$(Get-Date -Format 'yyyyMMdd_HHmmss').evtx" + + try { + wevtutil epl $Script:Config.EventLogName $ExportFile 2>$null + if (Test-Path $ExportFile) { + Write-Log "Exported DMEDP event log to: $ExportFile" -Level SUCCESS + } + } + catch { + Write-Log "Could not export DMEDP event log (may not exist): $_" -Level WARN + } +} + +# ============================================================================ +# DETECTION FUNCTIONS +# ============================================================================ + +function Get-BlockingRegistryValues { + $Findings = @() + + foreach ($Target in $Script:Config.RegistryTargets) { + $Path = $Target.Path + + if (-not (Test-Path $Path)) { + Write-Log "Path does not exist: $Path" -Level INFO + continue + } + + foreach ($Value in $Target.Values) { + try { + $CurrentValue = Get-ItemProperty -Path $Path -Name $Value.Name -ErrorAction SilentlyContinue + + if ($null -ne $CurrentValue) { + $ActualValue = $CurrentValue.($Value.Name) + + if ($ActualValue -eq $Value.BlockingValue) { + $Finding = [PSCustomObject]@{ + Path = $Path + Name = $Value.Name + Value = $ActualValue + BlockingValue = $Value.BlockingValue + Description = $Value.Description + IsBlocking = $true + } + $Findings += $Finding + Write-Log "BLOCKING: $($Value.Description) - $Path\$($Value.Name) = $ActualValue" -Level DETECT + } + else { + Write-Log "OK: $Path\$($Value.Name) = $ActualValue (not blocking)" -Level INFO + } + } + } + catch { + Write-Log "Error reading $Path\$($Value.Name): $_" -Level WARN + } + } + } + + return $Findings +} + +function Get-WmiMdmAuthority { + try { + $MdmAuthority = Get-CimInstance -Namespace "root/cimv2/mdm" -ClassName MDM_MgmtAuthority -ErrorAction SilentlyContinue + if ($MdmAuthority) { + Write-Log "WMI MDM Authority found: $($MdmAuthority | ConvertTo-Json -Compress)" -Level DETECT + return $MdmAuthority + } + else { + Write-Log "No WMI MDM Authority records found" -Level INFO + return $null + } + } + catch { + Write-Log "WMI MDM namespace not accessible (expected if never enrolled): $_" -Level INFO + return $null + } +} + +function Get-LegacyScheduledTasks { + $LegacyTasks = @() + + try { + $EntMgmtPath = "\Microsoft\Windows\EnterpriseMgmt\" + $Tasks = Get-ScheduledTask -TaskPath "$EntMgmtPath*" -ErrorAction SilentlyContinue + + foreach ($Task in $Tasks) { + Write-Log "Legacy scheduled task found: $($Task.TaskPath)$($Task.TaskName)" -Level DETECT + $LegacyTasks += $Task + } + + if ($LegacyTasks.Count -eq 0) { + Write-Log "No legacy EnterpriseMgmt scheduled tasks found" -Level INFO + } + } + catch { + Write-Log "Error checking scheduled tasks: $_" -Level WARN + } + + return $LegacyTasks +} + +# ============================================================================ +# REMEDIATION FUNCTIONS +# ============================================================================ + +function Remove-BlockingRegistryValue { + param( + [PSCustomObject]$Finding + ) + + if ($DetectOnly) { + Write-Log "DETECT-ONLY: Would remove $($Finding.Path)\$($Finding.Name)" -Level DETECT + return $true + } + + try { + Remove-ItemProperty -Path $Finding.Path -Name $Finding.Name -Force -ErrorAction Stop + Write-Log "REMOVED: $($Finding.Path)\$($Finding.Name)" -Level SUCCESS + return $true + } + catch { + Write-Log "FAILED to remove $($Finding.Path)\$($Finding.Name): $_" -Level ERROR + return $false + } +} + +function Remove-WmiMdmAuthority { + param( + $MdmAuthority + ) + + if ($DetectOnly) { + Write-Log "DETECT-ONLY: Would remove WMI MDM Authority record" -Level DETECT + return $true + } + + try { + $MdmAuthority | Remove-CimInstance -ErrorAction Stop + Write-Log "REMOVED: WMI MDM Authority record" -Level SUCCESS + return $true + } + catch { + Write-Log "FAILED to remove WMI MDM Authority: $_" -Level ERROR + return $false + } +} + +# ============================================================================ +# VALIDATION FUNCTIONS +# ============================================================================ + +function Get-DsregStatus { + Write-Log "Running dsregcmd /status for validation..." -Level INFO + + $DsregOutput = dsregcmd /status 2>&1 + $OutputFile = Join-Path $Script:Config.LogPath "dsregcmd_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt" + $DsregOutput | Out-File -FilePath $OutputFile -Encoding UTF8 + + Write-Log "dsregcmd output saved to: $OutputFile" -Level INFO + + # Parse key values + $AzureAdJoined = $DsregOutput | Select-String "AzureAdJoined\s*:\s*(\w+)" | ForEach-Object { $_.Matches.Groups[1].Value } + $DomainJoined = $DsregOutput | Select-String "DomainJoined\s*:\s*(\w+)" | ForEach-Object { $_.Matches.Groups[1].Value } + $WorkplaceJoined = $DsregOutput | Select-String "WorkplaceJoined\s*:\s*(\w+)" | ForEach-Object { $_.Matches.Groups[1].Value } + + Write-Log "Device State - DomainJoined: $DomainJoined | AzureAdJoined: $AzureAdJoined | WorkplaceJoined: $WorkplaceJoined" -Level INFO + + return @{ + AzureAdJoined = $AzureAdJoined + DomainJoined = $DomainJoined + WorkplaceJoined = $WorkplaceJoined + } +} + +# ============================================================================ +# MAIN EXECUTION +# ============================================================================ + +function Invoke-MdmEnrollmentRemediation { + Write-Log "============================================================" -Level INFO + Write-Log "MDM Enrollment Remediation Started" -Level INFO + Write-Log "Mode: $(if ($DetectOnly) { 'DETECT-ONLY' } else { 'REMEDIATION' })" -Level INFO + Write-Log "Computer: $env:COMPUTERNAME" -Level INFO + Write-Log "User Context: $env:USERNAME" -Level INFO + Write-Log "============================================================" -Level INFO + + # Capture initial state + Write-Log "--- INITIAL STATE ---" -Level INFO + $InitialState = Get-DsregStatus + + # Export event log before changes + Export-EventLog + + # Backup registry keys + Write-Log "--- BACKUP PHASE ---" -Level INFO + foreach ($Target in $Script:Config.RegistryTargets) { + Backup-RegistryKey -Path $Target.Path + } + + # Detection phase + Write-Log "--- DETECTION PHASE ---" -Level INFO + $BlockingValues = Get-BlockingRegistryValues + $WmiAuthority = if ($IncludeWmiCleanup) { Get-WmiMdmAuthority } else { $null } + $LegacyTasks = Get-LegacyScheduledTasks + + # Summary of findings + Write-Log "--- FINDINGS SUMMARY ---" -Level INFO + Write-Log "Blocking registry values found: $($BlockingValues.Count)" -Level $(if ($BlockingValues.Count -gt 0) { "WARN" } else { "SUCCESS" }) + Write-Log "WMI MDM Authority present: $(if ($WmiAuthority) { 'Yes' } else { 'No' })" -Level INFO + Write-Log "Legacy scheduled tasks found: $($LegacyTasks.Count)" -Level $(if ($LegacyTasks.Count -gt 0) { "WARN" } else { "INFO" }) + + if ($DetectOnly) { + Write-Log "--- DETECT-ONLY MODE - NO CHANGES MADE ---" -Level DETECT + $ExitCode = if ($BlockingValues.Count -gt 0) { 1 } else { 0 } + } + else { + # Remediation phase + Write-Log "--- REMEDIATION PHASE ---" -Level INFO + $RemediationResults = @() + + foreach ($Finding in $BlockingValues) { + $Result = Remove-BlockingRegistryValue -Finding $Finding + $RemediationResults += @{ Finding = $Finding; Success = $Result } + } + + if ($IncludeWmiCleanup -and $WmiAuthority) { + $WmiResult = Remove-WmiMdmAuthority -MdmAuthority $WmiAuthority + $RemediationResults += @{ Finding = "WMI MDM Authority"; Success = $WmiResult } + } + + # Note about scheduled tasks (manual review recommended) + if ($LegacyTasks.Count -gt 0) { + Write-Log "Legacy scheduled tasks require manual review - not auto-removed" -Level WARN + } + + # Post-remediation validation + Write-Log "--- POST-REMEDIATION VALIDATION ---" -Level INFO + $FinalState = Get-DsregStatus + + # Re-check for blocking values + $RemainingBlocks = Get-BlockingRegistryValues + + $SuccessCount = ($RemediationResults | Where-Object { $_.Success }).Count + $FailCount = ($RemediationResults | Where-Object { -not $_.Success }).Count + + Write-Log "--- REMEDIATION SUMMARY ---" -Level INFO + Write-Log "Successful remediations: $SuccessCount" -Level SUCCESS + Write-Log "Failed remediations: $FailCount" -Level $(if ($FailCount -gt 0) { "ERROR" } else { "INFO" }) + Write-Log "Remaining blocks: $($RemainingBlocks.Count)" -Level $(if ($RemainingBlocks.Count -gt 0) { "ERROR" } else { "SUCCESS" }) + + $ExitCode = if ($FailCount -gt 0 -or $RemainingBlocks.Count -gt 0) { 1 } else { 0 } + } + + Write-Log "============================================================" -Level INFO + Write-Log "MDM Enrollment Remediation Completed - Exit Code: $ExitCode" -Level INFO + Write-Log "Log file: $Script:LogFilePath" -Level INFO + Write-Log "============================================================" -Level INFO + + return $ExitCode +} + +# ============================================================================ +# ENTRY POINT +# ============================================================================ + +try { + Initialize-Logging + $ExitCode = Invoke-MdmEnrollmentRemediation + Stop-Transcript + exit $ExitCode +} +catch { + Write-Log "FATAL ERROR: $_" -Level ERROR + Write-Log $_.ScriptStackTrace -Level ERROR + Stop-Transcript + exit 99 +} From 3eece531e570a8836d05af000f18fb599a342c6e Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:05:41 -0400 Subject: [PATCH 098/107] Add GroupPolicyAnalyticsCleanup.js from archived NodeJS repo --- GroupPolicyAnalyticsCleanup.js | 105 +++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 GroupPolicyAnalyticsCleanup.js diff --git a/GroupPolicyAnalyticsCleanup.js b/GroupPolicyAnalyticsCleanup.js new file mode 100644 index 0000000..26f0915 --- /dev/null +++ b/GroupPolicyAnalyticsCleanup.js @@ -0,0 +1,105 @@ +const { Client } = require("@microsoft/microsoft-graph-client"); +require("isomorphic-fetch"); +const readline = require("readline"); +const msal = require("@azure/msal-node"); + +// Authentication configuration +const msalConfig = { + auth: { + clientId: "<>", + clientSecret: "<>", + authority: "https://login.microsoftonline.com/<>" + } +}; + +const tokenRequest = { + scopes: ["https://graph.microsoft.com/.default"] +}; + +async function getAuthenticatedClient() { + const cca = new msal.ConfidentialClientApplication(msalConfig); + try { + const authResult = await cca.acquireTokenByClientCredential(tokenRequest); + console.log("Access Token:", authResult.accessToken); + const client = Client.init({ + authProvider: (done) => { + done(null, authResult.accessToken); + } + }); + return client; + } catch (error) { + console.error("Authentication error:", error); + throw error; + } +} + +async function getReports(client) { + try { + const reports = await client + .api("/deviceManagement/groupPolicyMigrationReports") + .version("beta") // Specifying the beta version + .get(); + return reports.value; + } catch (error) { + console.error("Error fetching reports:", error); + return []; + } +} + +async function deleteReport(client, reportId) { + try { + await client + .api(`/deviceManagement/groupPolicyMigrationReports/${reportId}`) + .version("beta") // Specifying the beta version for deletion as well + .delete(); + console.log(`Successfully deleted report with ID: ${reportId}`); + } catch (error) { + console.error(`Failed to delete report with ID: ${reportId} - ${error}`); + } +} + +async function selectReports(reports) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + console.log("Available reports:"); + reports.forEach((report, index) => { + console.log(`${index + 1}. ${report.displayName || report.id}`); + }); + + const answer = await new Promise((resolve) => { + rl.question( + "Enter the numbers of the reports you want to delete (comma-separated): ", + resolve + ); + }); + + rl.close(); + + const selectedIndexes = answer.split(",").map((n) => parseInt(n.trim()) - 1); + return selectedIndexes.map((index) => reports[index]).filter(Boolean); +} + +async function main() { + try { + const client = await getAuthenticatedClient(); + const reports = await getReports(client); + + if (reports.length === 0) { + console.log("No reports found."); + return; + } + + const selectedReports = await selectReports(reports); + + for (const report of selectedReports) { + await deleteReport(client, report.id); + } + } catch (error) { + console.error("An error occurred:", error); + } +} + +main(); From 758a6926a4d953dcd44b54d0ec0b244edbfae163 Mon Sep 17 00:00:00 2001 From: Hal-Scriptville <161669964+Hal-Scriptville@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:05:42 -0400 Subject: [PATCH 099/107] Add intune_enrollment.bat from archived OtherScripts repo --- intune_enrollment.bat | 1 + 1 file changed, 1 insertion(+) create mode 100644 intune_enrollment.bat diff --git a/intune_enrollment.bat b/intune_enrollment.bat new file mode 100644 index 0000000..fc69851 --- /dev/null +++ b/intune_enrollment.bat @@ -0,0 +1 @@ +c:\windows\system32\deviceenroller.exe /c /AutoEnrollMDM \ No newline at end of file From 18569d85236a883af1208a261bc9a56ddd09a5ac Mon Sep 17 00:00:00 2001 From: Hal Kurz Date: Fri, 27 Mar 2026 14:11:33 -0400 Subject: [PATCH 100/107] Reorganize scripts into folder structure - AD/: Active Directory, GPO, ADFS, NDES scripts - BitLocker/: BitLocker management scripts - Intune/: Intune enrollment, AutoPilot, AppX, runbooks - ProactiveRemediation/Detection/: Intune detection scripts - ProactiveRemediation/Remediation/: Intune remediation scripts - WindowsUpdate/: WSUS, AU, update management scripts - Utilities/: Miscellaneous one-off scripts - Resolved case-collision duplicates (WinGet/Winget) --- Add-ADFSDeviceClaimRules.ps1 => AD/Add-ADFSDeviceClaimRules.ps1 | 0 Apply-NameTemplate.ps1 => AD/Apply-NameTemplate.ps1 | 0 Check-ADHealth.ps1 => AD/Check-ADHealth.ps1 | 0 Check-ADHealth_NEW.ps1 => AD/Check-ADHealth_NEW.ps1 | 0 Check-ADHealth_Updated.ps1 => AD/Check-ADHealth_Updated.ps1 | 0 Check-NameTemplate.ps1 => AD/Check-NameTemplate.ps1 | 0 Configure-NDESPrereqs.ps1 => AD/Configure-NDESPrereqs.ps1 | 0 .../Delete-GroupPolicyAnalyticsReports.ps1 | 0 Get-ComputerTimestamps.ps1 => AD/Get-ComputerTimestamps.ps1 | 0 Get-GPOReports.ps1 => AD/Get-GPOReports.ps1 | 0 .../GroupPolicyAnalyticsCleanup.js | 0 .../Sync-OUToSecurityGroups-Scheduled.ps1 | 0 .../Validate-NDESConfiguration.ps1 | 0 .../Escrow-BitLocker_to_AAD.ps1 | 0 .../Find-MissingKeyInBitlocker.ps1 | 0 Get-BitLockerInfo.ps1 => BitLocker/Get-BitLockerInfo.ps1 | 0 Initiate-BitLocker.ps1 => BitLocker/Initiate-BitLocker.ps1 | 0 Build-IntuneAppBuilder.ps1 => Intune/Build-IntuneAppBuilder.ps1 | 0 Clean-IntuneEnrollment.ps1 => Intune/Clean-IntuneEnrollment.ps1 | 0 Create-StaleDeviceGroup.ps1 => Intune/Create-StaleDeviceGroup.ps1 | 0 .../Delete-StaleDeviceByGroup.ps1 | 0 Enhanced-AppX-Cleanup.ps1 => Intune/Enhanced-AppX-Cleanup.ps1 | 0 Enroll-Intune.ps1 => Intune/Enroll-Intune.ps1 | 0 Force-Sync.ps1 => Intune/Force-Sync.ps1 | 0 Get-InstalledSoftware.ps1 => Intune/Get-InstalledSoftware.ps1 | 0 Get-UninstallStrings2.ps1 => Intune/Get-UninstallStrings2.ps1 | 0 .../Intune-AutoEnrollment-HybridJoined.ps1 | 0 .../Intune-AutoEnrollment-Usage.md | 0 Intune-AutoEnrollment.ps1 => Intune/Intune-AutoEnrollment.ps1 | 0 .../Intune-AutomationRunbookv1_0.ps1 | 0 .../Intune-AutomationRunbookv2_1.ps1 | 0 Nuclear-Appx-Cleanup.ps1 => Intune/Nuclear-Appx-Cleanup.ps1 | 0 Set-AutomaticTimezone.ps1 => Intune/Set-AutomaticTimezone.ps1 | 0 Set-EnrollmentAttribute.ps1 => Intune/Set-EnrollmentAttribute.ps1 | 0 .../Set-EnrollmentAttribute_RANGE.ps1 | 0 Sysprep-PreCleanup.ps1 => Intune/Sysprep-PreCleanup.ps1 | 0 intune_enrollment.bat => Intune/intune_enrollment.bat | 0 .../Detection/Detect-CarbonBlack.ps1 | 0 .../Detection/Detect-DiskSpace.ps1 | 0 .../Detection/Detect-FixedDrive.ps1 | 0 Detect-IME.ps1 => ProactiveRemediation/Detection/Detect-IME.ps1 | 0 .../Detection/Detect-IntuneCache.ps1 | 0 .../Detection/Detect-IntuneCoManageSettings.ps1 | 0 .../Detection/Detect-IntuneUpdateSettings.ps1 | 0 .../Detection/Detect-MdmEnrollmentBlock.ps1 | 0 .../Detection/Detect-SCCMClientAgent.ps1 | 0 .../Detection/Detect-TimeoutWarning.ps1 | 0 .../Detection/Detect-WUEntries.ps1 | 0 .../Detection/Detect-WUServer.ps1 | 0 .../Detection/Detect-WingetUpdates.ps1 | 0 .../Detection/Detect-WiredAutoConfig.ps1 | 0 .../Detection/DetectionMethod-Process.ps1 | 0 .../Remediation/Remediate-AU.ps1 | 0 .../Remediation/Remediate-CarbonBlack.ps1 | 0 .../Remediation/Remediate-DisableDualScan.ps1 | 0 .../Remediation/Remediate-DiskSpace.ps1 | 0 .../Remediation/Remediate-IME.ps1 | 0 .../Remediation/Remediate-IntuneCache.ps1 | 0 .../Remediation/Remediate-IntuneCoManageSettings.ps1 | 0 .../Remediation/Remediate-IntuneUpdateSettings.ps1 | 0 .../Remediation/Remediate-MdmEnrollmentBlock.ps1 | 0 .../Remediation/Remediate-NoAutoUpdate.ps1 | 0 .../Remediation/Remediate-SCCMClientAgent.ps1 | 0 .../Remediation/Remediate-TimeoutWarning.ps1 | 0 .../Remediation/Remediate-WSUS_LastOnlineScan.ps1 | 0 .../Remediation/Remediate-WUEntries.ps1 | 0 .../Remediation/Remediate-WUServer.ps1 | 0 .../Remediation/Remediate-WingetUpdates.ps1 | 0 .../Remediation/Remediate-WiredAutoConfig.ps1 | 0 Backup-Permissions.ps1 => Utilities/Backup-Permissions.ps1 | 0 Change-Ownership.ps1 => Utilities/Change-Ownership.ps1 | 0 Create-100Files.ps1 => Utilities/Create-100Files.ps1 | 0 .../Discover-Printers_CIM_WMI.ps1 | 0 Discover-Printers_PS.ps1 => Utilities/Discover-Printers_PS.ps1 | 0 .../Exchange-MailboxRetentionPolicyManager.ps1 | 0 Get-Counts.ps1 => Utilities/Get-Counts.ps1 | 0 .../Install-NotepadPlusPlus.ps1 | 0 Check-AU.ps1 => WindowsUpdate/Check-AU.ps1 | 0 .../Check-DisableDualScan.ps1 | 0 Check-NoAutoUpdate.ps1 => WindowsUpdate/Check-NoAutoUpdate.ps1 | 0 .../Check-WSUS_LastOnlineScan.ps1 | 0 WindowsUpdate-Reset.ps1 => WindowsUpdate/WindowsUpdate-Reset.ps1 | 0 82 files changed, 0 insertions(+), 0 deletions(-) rename Add-ADFSDeviceClaimRules.ps1 => AD/Add-ADFSDeviceClaimRules.ps1 (100%) rename Apply-NameTemplate.ps1 => AD/Apply-NameTemplate.ps1 (100%) rename Check-ADHealth.ps1 => AD/Check-ADHealth.ps1 (100%) rename Check-ADHealth_NEW.ps1 => AD/Check-ADHealth_NEW.ps1 (100%) rename Check-ADHealth_Updated.ps1 => AD/Check-ADHealth_Updated.ps1 (100%) rename Check-NameTemplate.ps1 => AD/Check-NameTemplate.ps1 (100%) rename Configure-NDESPrereqs.ps1 => AD/Configure-NDESPrereqs.ps1 (100%) rename Delete-GroupPolicyAnalyticsReports.ps1 => AD/Delete-GroupPolicyAnalyticsReports.ps1 (100%) rename Get-ComputerTimestamps.ps1 => AD/Get-ComputerTimestamps.ps1 (100%) rename Get-GPOReports.ps1 => AD/Get-GPOReports.ps1 (100%) rename GroupPolicyAnalyticsCleanup.js => AD/GroupPolicyAnalyticsCleanup.js (100%) rename Sync-OUToSecurityGroups-Scheduled.ps1 => AD/Sync-OUToSecurityGroups-Scheduled.ps1 (100%) rename Validate-NDESConfiguration.ps1 => AD/Validate-NDESConfiguration.ps1 (100%) rename Escrow-BitLocker_to_AAD.ps1 => BitLocker/Escrow-BitLocker_to_AAD.ps1 (100%) rename Find-MissingKeyInBitlocker.ps1 => BitLocker/Find-MissingKeyInBitlocker.ps1 (100%) rename Get-BitLockerInfo.ps1 => BitLocker/Get-BitLockerInfo.ps1 (100%) rename Initiate-BitLocker.ps1 => BitLocker/Initiate-BitLocker.ps1 (100%) rename Build-IntuneAppBuilder.ps1 => Intune/Build-IntuneAppBuilder.ps1 (100%) rename Clean-IntuneEnrollment.ps1 => Intune/Clean-IntuneEnrollment.ps1 (100%) rename Create-StaleDeviceGroup.ps1 => Intune/Create-StaleDeviceGroup.ps1 (100%) rename Delete-StaleDeviceByGroup.ps1 => Intune/Delete-StaleDeviceByGroup.ps1 (100%) rename Enhanced-AppX-Cleanup.ps1 => Intune/Enhanced-AppX-Cleanup.ps1 (100%) rename Enroll-Intune.ps1 => Intune/Enroll-Intune.ps1 (100%) rename Force-Sync.ps1 => Intune/Force-Sync.ps1 (100%) rename Get-InstalledSoftware.ps1 => Intune/Get-InstalledSoftware.ps1 (100%) rename Get-UninstallStrings2.ps1 => Intune/Get-UninstallStrings2.ps1 (100%) rename Intune-AutoEnrollment-HybridJoined.ps1 => Intune/Intune-AutoEnrollment-HybridJoined.ps1 (100%) rename Intune-AutoEnrollment-Usage.md => Intune/Intune-AutoEnrollment-Usage.md (100%) rename Intune-AutoEnrollment.ps1 => Intune/Intune-AutoEnrollment.ps1 (100%) rename Intune-AutomationRunbookv1_0.ps1 => Intune/Intune-AutomationRunbookv1_0.ps1 (100%) rename Intune-AutomationRunbookv2_1.ps1 => Intune/Intune-AutomationRunbookv2_1.ps1 (100%) rename Nuclear-Appx-Cleanup.ps1 => Intune/Nuclear-Appx-Cleanup.ps1 (100%) rename Set-AutomaticTimezone.ps1 => Intune/Set-AutomaticTimezone.ps1 (100%) rename Set-EnrollmentAttribute.ps1 => Intune/Set-EnrollmentAttribute.ps1 (100%) rename Set-EnrollmentAttribute_RANGE.ps1 => Intune/Set-EnrollmentAttribute_RANGE.ps1 (100%) rename Sysprep-PreCleanup.ps1 => Intune/Sysprep-PreCleanup.ps1 (100%) rename intune_enrollment.bat => Intune/intune_enrollment.bat (100%) rename Detect-CarbonBlack.ps1 => ProactiveRemediation/Detection/Detect-CarbonBlack.ps1 (100%) rename Detect-DiskSpace.ps1 => ProactiveRemediation/Detection/Detect-DiskSpace.ps1 (100%) rename Detect-FixedDrive.ps1 => ProactiveRemediation/Detection/Detect-FixedDrive.ps1 (100%) rename Detect-IME.ps1 => ProactiveRemediation/Detection/Detect-IME.ps1 (100%) rename Detect-IntuneCache.ps1 => ProactiveRemediation/Detection/Detect-IntuneCache.ps1 (100%) rename Detect-IntuneCoManageSettings.ps1 => ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 (100%) rename Detect-IntuneUpdateSettings.ps1 => ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 (100%) rename Detect-MdmEnrollmentBlock.ps1 => ProactiveRemediation/Detection/Detect-MdmEnrollmentBlock.ps1 (100%) rename Detect-SCCMClientAgent.ps1 => ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 (100%) rename Detect-TimeoutWarning.ps1 => ProactiveRemediation/Detection/Detect-TimeoutWarning.ps1 (100%) rename Detect-WUEntries.ps1 => ProactiveRemediation/Detection/Detect-WUEntries.ps1 (100%) rename Detect-WUServer.ps1 => ProactiveRemediation/Detection/Detect-WUServer.ps1 (100%) rename Detect-WingetUpdates.ps1 => ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 (100%) rename Detect-WiredAutoConfig.ps1 => ProactiveRemediation/Detection/Detect-WiredAutoConfig.ps1 (100%) rename DetectionMethod-Process.ps1 => ProactiveRemediation/Detection/DetectionMethod-Process.ps1 (100%) rename Remediate-AU.ps1 => ProactiveRemediation/Remediation/Remediate-AU.ps1 (100%) rename Remediate-CarbonBlack.ps1 => ProactiveRemediation/Remediation/Remediate-CarbonBlack.ps1 (100%) rename Remediate-DisableDualScan.ps1 => ProactiveRemediation/Remediation/Remediate-DisableDualScan.ps1 (100%) rename Remediate-DiskSpace.ps1 => ProactiveRemediation/Remediation/Remediate-DiskSpace.ps1 (100%) rename Remediate-IME.ps1 => ProactiveRemediation/Remediation/Remediate-IME.ps1 (100%) rename Remediate-IntuneCache.ps1 => ProactiveRemediation/Remediation/Remediate-IntuneCache.ps1 (100%) rename Remediate-IntuneCoManageSettings.ps1 => ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 (100%) rename Remediate-IntuneUpdateSettings.ps1 => ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 (100%) rename Remediate-MdmEnrollmentBlock.ps1 => ProactiveRemediation/Remediation/Remediate-MdmEnrollmentBlock.ps1 (100%) rename Remediate-NoAutoUpdate.ps1 => ProactiveRemediation/Remediation/Remediate-NoAutoUpdate.ps1 (100%) rename Remediate-SCCMClientAgent.ps1 => ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 (100%) rename Remediate-TimeoutWarning.ps1 => ProactiveRemediation/Remediation/Remediate-TimeoutWarning.ps1 (100%) rename Remediate-WSUS_LastOnlineScan.ps1 => ProactiveRemediation/Remediation/Remediate-WSUS_LastOnlineScan.ps1 (100%) rename Remediate-WUEntries.ps1 => ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 (100%) rename Remediate-WUServer.ps1 => ProactiveRemediation/Remediation/Remediate-WUServer.ps1 (100%) rename Remediate-WingetUpdates.ps1 => ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 (100%) rename Remediate-WiredAutoConfig.ps1 => ProactiveRemediation/Remediation/Remediate-WiredAutoConfig.ps1 (100%) rename Backup-Permissions.ps1 => Utilities/Backup-Permissions.ps1 (100%) rename Change-Ownership.ps1 => Utilities/Change-Ownership.ps1 (100%) rename Create-100Files.ps1 => Utilities/Create-100Files.ps1 (100%) rename Discover-Printers_CIM_WMI.ps1 => Utilities/Discover-Printers_CIM_WMI.ps1 (100%) rename Discover-Printers_PS.ps1 => Utilities/Discover-Printers_PS.ps1 (100%) rename Exchange-MailboxRetentionPolicyManager.ps1 => Utilities/Exchange-MailboxRetentionPolicyManager.ps1 (100%) rename Get-Counts.ps1 => Utilities/Get-Counts.ps1 (100%) rename Install-NotepadPlusPlus.ps1 => Utilities/Install-NotepadPlusPlus.ps1 (100%) rename Check-AU.ps1 => WindowsUpdate/Check-AU.ps1 (100%) rename Check-DisableDualScan.ps1 => WindowsUpdate/Check-DisableDualScan.ps1 (100%) rename Check-NoAutoUpdate.ps1 => WindowsUpdate/Check-NoAutoUpdate.ps1 (100%) rename Check-WSUS_LastOnlineScan.ps1 => WindowsUpdate/Check-WSUS_LastOnlineScan.ps1 (100%) rename WindowsUpdate-Reset.ps1 => WindowsUpdate/WindowsUpdate-Reset.ps1 (100%) diff --git a/Add-ADFSDeviceClaimRules.ps1 b/AD/Add-ADFSDeviceClaimRules.ps1 similarity index 100% rename from Add-ADFSDeviceClaimRules.ps1 rename to AD/Add-ADFSDeviceClaimRules.ps1 diff --git a/Apply-NameTemplate.ps1 b/AD/Apply-NameTemplate.ps1 similarity index 100% rename from Apply-NameTemplate.ps1 rename to AD/Apply-NameTemplate.ps1 diff --git a/Check-ADHealth.ps1 b/AD/Check-ADHealth.ps1 similarity index 100% rename from Check-ADHealth.ps1 rename to AD/Check-ADHealth.ps1 diff --git a/Check-ADHealth_NEW.ps1 b/AD/Check-ADHealth_NEW.ps1 similarity index 100% rename from Check-ADHealth_NEW.ps1 rename to AD/Check-ADHealth_NEW.ps1 diff --git a/Check-ADHealth_Updated.ps1 b/AD/Check-ADHealth_Updated.ps1 similarity index 100% rename from Check-ADHealth_Updated.ps1 rename to AD/Check-ADHealth_Updated.ps1 diff --git a/Check-NameTemplate.ps1 b/AD/Check-NameTemplate.ps1 similarity index 100% rename from Check-NameTemplate.ps1 rename to AD/Check-NameTemplate.ps1 diff --git a/Configure-NDESPrereqs.ps1 b/AD/Configure-NDESPrereqs.ps1 similarity index 100% rename from Configure-NDESPrereqs.ps1 rename to AD/Configure-NDESPrereqs.ps1 diff --git a/Delete-GroupPolicyAnalyticsReports.ps1 b/AD/Delete-GroupPolicyAnalyticsReports.ps1 similarity index 100% rename from Delete-GroupPolicyAnalyticsReports.ps1 rename to AD/Delete-GroupPolicyAnalyticsReports.ps1 diff --git a/Get-ComputerTimestamps.ps1 b/AD/Get-ComputerTimestamps.ps1 similarity index 100% rename from Get-ComputerTimestamps.ps1 rename to AD/Get-ComputerTimestamps.ps1 diff --git a/Get-GPOReports.ps1 b/AD/Get-GPOReports.ps1 similarity index 100% rename from Get-GPOReports.ps1 rename to AD/Get-GPOReports.ps1 diff --git a/GroupPolicyAnalyticsCleanup.js b/AD/GroupPolicyAnalyticsCleanup.js similarity index 100% rename from GroupPolicyAnalyticsCleanup.js rename to AD/GroupPolicyAnalyticsCleanup.js diff --git a/Sync-OUToSecurityGroups-Scheduled.ps1 b/AD/Sync-OUToSecurityGroups-Scheduled.ps1 similarity index 100% rename from Sync-OUToSecurityGroups-Scheduled.ps1 rename to AD/Sync-OUToSecurityGroups-Scheduled.ps1 diff --git a/Validate-NDESConfiguration.ps1 b/AD/Validate-NDESConfiguration.ps1 similarity index 100% rename from Validate-NDESConfiguration.ps1 rename to AD/Validate-NDESConfiguration.ps1 diff --git a/Escrow-BitLocker_to_AAD.ps1 b/BitLocker/Escrow-BitLocker_to_AAD.ps1 similarity index 100% rename from Escrow-BitLocker_to_AAD.ps1 rename to BitLocker/Escrow-BitLocker_to_AAD.ps1 diff --git a/Find-MissingKeyInBitlocker.ps1 b/BitLocker/Find-MissingKeyInBitlocker.ps1 similarity index 100% rename from Find-MissingKeyInBitlocker.ps1 rename to BitLocker/Find-MissingKeyInBitlocker.ps1 diff --git a/Get-BitLockerInfo.ps1 b/BitLocker/Get-BitLockerInfo.ps1 similarity index 100% rename from Get-BitLockerInfo.ps1 rename to BitLocker/Get-BitLockerInfo.ps1 diff --git a/Initiate-BitLocker.ps1 b/BitLocker/Initiate-BitLocker.ps1 similarity index 100% rename from Initiate-BitLocker.ps1 rename to BitLocker/Initiate-BitLocker.ps1 diff --git a/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 similarity index 100% rename from Build-IntuneAppBuilder.ps1 rename to Intune/Build-IntuneAppBuilder.ps1 diff --git a/Clean-IntuneEnrollment.ps1 b/Intune/Clean-IntuneEnrollment.ps1 similarity index 100% rename from Clean-IntuneEnrollment.ps1 rename to Intune/Clean-IntuneEnrollment.ps1 diff --git a/Create-StaleDeviceGroup.ps1 b/Intune/Create-StaleDeviceGroup.ps1 similarity index 100% rename from Create-StaleDeviceGroup.ps1 rename to Intune/Create-StaleDeviceGroup.ps1 diff --git a/Delete-StaleDeviceByGroup.ps1 b/Intune/Delete-StaleDeviceByGroup.ps1 similarity index 100% rename from Delete-StaleDeviceByGroup.ps1 rename to Intune/Delete-StaleDeviceByGroup.ps1 diff --git a/Enhanced-AppX-Cleanup.ps1 b/Intune/Enhanced-AppX-Cleanup.ps1 similarity index 100% rename from Enhanced-AppX-Cleanup.ps1 rename to Intune/Enhanced-AppX-Cleanup.ps1 diff --git a/Enroll-Intune.ps1 b/Intune/Enroll-Intune.ps1 similarity index 100% rename from Enroll-Intune.ps1 rename to Intune/Enroll-Intune.ps1 diff --git a/Force-Sync.ps1 b/Intune/Force-Sync.ps1 similarity index 100% rename from Force-Sync.ps1 rename to Intune/Force-Sync.ps1 diff --git a/Get-InstalledSoftware.ps1 b/Intune/Get-InstalledSoftware.ps1 similarity index 100% rename from Get-InstalledSoftware.ps1 rename to Intune/Get-InstalledSoftware.ps1 diff --git a/Get-UninstallStrings2.ps1 b/Intune/Get-UninstallStrings2.ps1 similarity index 100% rename from Get-UninstallStrings2.ps1 rename to Intune/Get-UninstallStrings2.ps1 diff --git a/Intune-AutoEnrollment-HybridJoined.ps1 b/Intune/Intune-AutoEnrollment-HybridJoined.ps1 similarity index 100% rename from Intune-AutoEnrollment-HybridJoined.ps1 rename to Intune/Intune-AutoEnrollment-HybridJoined.ps1 diff --git a/Intune-AutoEnrollment-Usage.md b/Intune/Intune-AutoEnrollment-Usage.md similarity index 100% rename from Intune-AutoEnrollment-Usage.md rename to Intune/Intune-AutoEnrollment-Usage.md diff --git a/Intune-AutoEnrollment.ps1 b/Intune/Intune-AutoEnrollment.ps1 similarity index 100% rename from Intune-AutoEnrollment.ps1 rename to Intune/Intune-AutoEnrollment.ps1 diff --git a/Intune-AutomationRunbookv1_0.ps1 b/Intune/Intune-AutomationRunbookv1_0.ps1 similarity index 100% rename from Intune-AutomationRunbookv1_0.ps1 rename to Intune/Intune-AutomationRunbookv1_0.ps1 diff --git a/Intune-AutomationRunbookv2_1.ps1 b/Intune/Intune-AutomationRunbookv2_1.ps1 similarity index 100% rename from Intune-AutomationRunbookv2_1.ps1 rename to Intune/Intune-AutomationRunbookv2_1.ps1 diff --git a/Nuclear-Appx-Cleanup.ps1 b/Intune/Nuclear-Appx-Cleanup.ps1 similarity index 100% rename from Nuclear-Appx-Cleanup.ps1 rename to Intune/Nuclear-Appx-Cleanup.ps1 diff --git a/Set-AutomaticTimezone.ps1 b/Intune/Set-AutomaticTimezone.ps1 similarity index 100% rename from Set-AutomaticTimezone.ps1 rename to Intune/Set-AutomaticTimezone.ps1 diff --git a/Set-EnrollmentAttribute.ps1 b/Intune/Set-EnrollmentAttribute.ps1 similarity index 100% rename from Set-EnrollmentAttribute.ps1 rename to Intune/Set-EnrollmentAttribute.ps1 diff --git a/Set-EnrollmentAttribute_RANGE.ps1 b/Intune/Set-EnrollmentAttribute_RANGE.ps1 similarity index 100% rename from Set-EnrollmentAttribute_RANGE.ps1 rename to Intune/Set-EnrollmentAttribute_RANGE.ps1 diff --git a/Sysprep-PreCleanup.ps1 b/Intune/Sysprep-PreCleanup.ps1 similarity index 100% rename from Sysprep-PreCleanup.ps1 rename to Intune/Sysprep-PreCleanup.ps1 diff --git a/intune_enrollment.bat b/Intune/intune_enrollment.bat similarity index 100% rename from intune_enrollment.bat rename to Intune/intune_enrollment.bat diff --git a/Detect-CarbonBlack.ps1 b/ProactiveRemediation/Detection/Detect-CarbonBlack.ps1 similarity index 100% rename from Detect-CarbonBlack.ps1 rename to ProactiveRemediation/Detection/Detect-CarbonBlack.ps1 diff --git a/Detect-DiskSpace.ps1 b/ProactiveRemediation/Detection/Detect-DiskSpace.ps1 similarity index 100% rename from Detect-DiskSpace.ps1 rename to ProactiveRemediation/Detection/Detect-DiskSpace.ps1 diff --git a/Detect-FixedDrive.ps1 b/ProactiveRemediation/Detection/Detect-FixedDrive.ps1 similarity index 100% rename from Detect-FixedDrive.ps1 rename to ProactiveRemediation/Detection/Detect-FixedDrive.ps1 diff --git a/Detect-IME.ps1 b/ProactiveRemediation/Detection/Detect-IME.ps1 similarity index 100% rename from Detect-IME.ps1 rename to ProactiveRemediation/Detection/Detect-IME.ps1 diff --git a/Detect-IntuneCache.ps1 b/ProactiveRemediation/Detection/Detect-IntuneCache.ps1 similarity index 100% rename from Detect-IntuneCache.ps1 rename to ProactiveRemediation/Detection/Detect-IntuneCache.ps1 diff --git a/Detect-IntuneCoManageSettings.ps1 b/ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 similarity index 100% rename from Detect-IntuneCoManageSettings.ps1 rename to ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 diff --git a/Detect-IntuneUpdateSettings.ps1 b/ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 similarity index 100% rename from Detect-IntuneUpdateSettings.ps1 rename to ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 diff --git a/Detect-MdmEnrollmentBlock.ps1 b/ProactiveRemediation/Detection/Detect-MdmEnrollmentBlock.ps1 similarity index 100% rename from Detect-MdmEnrollmentBlock.ps1 rename to ProactiveRemediation/Detection/Detect-MdmEnrollmentBlock.ps1 diff --git a/Detect-SCCMClientAgent.ps1 b/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 similarity index 100% rename from Detect-SCCMClientAgent.ps1 rename to ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 diff --git a/Detect-TimeoutWarning.ps1 b/ProactiveRemediation/Detection/Detect-TimeoutWarning.ps1 similarity index 100% rename from Detect-TimeoutWarning.ps1 rename to ProactiveRemediation/Detection/Detect-TimeoutWarning.ps1 diff --git a/Detect-WUEntries.ps1 b/ProactiveRemediation/Detection/Detect-WUEntries.ps1 similarity index 100% rename from Detect-WUEntries.ps1 rename to ProactiveRemediation/Detection/Detect-WUEntries.ps1 diff --git a/Detect-WUServer.ps1 b/ProactiveRemediation/Detection/Detect-WUServer.ps1 similarity index 100% rename from Detect-WUServer.ps1 rename to ProactiveRemediation/Detection/Detect-WUServer.ps1 diff --git a/Detect-WingetUpdates.ps1 b/ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 similarity index 100% rename from Detect-WingetUpdates.ps1 rename to ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 diff --git a/Detect-WiredAutoConfig.ps1 b/ProactiveRemediation/Detection/Detect-WiredAutoConfig.ps1 similarity index 100% rename from Detect-WiredAutoConfig.ps1 rename to ProactiveRemediation/Detection/Detect-WiredAutoConfig.ps1 diff --git a/DetectionMethod-Process.ps1 b/ProactiveRemediation/Detection/DetectionMethod-Process.ps1 similarity index 100% rename from DetectionMethod-Process.ps1 rename to ProactiveRemediation/Detection/DetectionMethod-Process.ps1 diff --git a/Remediate-AU.ps1 b/ProactiveRemediation/Remediation/Remediate-AU.ps1 similarity index 100% rename from Remediate-AU.ps1 rename to ProactiveRemediation/Remediation/Remediate-AU.ps1 diff --git a/Remediate-CarbonBlack.ps1 b/ProactiveRemediation/Remediation/Remediate-CarbonBlack.ps1 similarity index 100% rename from Remediate-CarbonBlack.ps1 rename to ProactiveRemediation/Remediation/Remediate-CarbonBlack.ps1 diff --git a/Remediate-DisableDualScan.ps1 b/ProactiveRemediation/Remediation/Remediate-DisableDualScan.ps1 similarity index 100% rename from Remediate-DisableDualScan.ps1 rename to ProactiveRemediation/Remediation/Remediate-DisableDualScan.ps1 diff --git a/Remediate-DiskSpace.ps1 b/ProactiveRemediation/Remediation/Remediate-DiskSpace.ps1 similarity index 100% rename from Remediate-DiskSpace.ps1 rename to ProactiveRemediation/Remediation/Remediate-DiskSpace.ps1 diff --git a/Remediate-IME.ps1 b/ProactiveRemediation/Remediation/Remediate-IME.ps1 similarity index 100% rename from Remediate-IME.ps1 rename to ProactiveRemediation/Remediation/Remediate-IME.ps1 diff --git a/Remediate-IntuneCache.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneCache.ps1 similarity index 100% rename from Remediate-IntuneCache.ps1 rename to ProactiveRemediation/Remediation/Remediate-IntuneCache.ps1 diff --git a/Remediate-IntuneCoManageSettings.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 similarity index 100% rename from Remediate-IntuneCoManageSettings.ps1 rename to ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 diff --git a/Remediate-IntuneUpdateSettings.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 similarity index 100% rename from Remediate-IntuneUpdateSettings.ps1 rename to ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 diff --git a/Remediate-MdmEnrollmentBlock.ps1 b/ProactiveRemediation/Remediation/Remediate-MdmEnrollmentBlock.ps1 similarity index 100% rename from Remediate-MdmEnrollmentBlock.ps1 rename to ProactiveRemediation/Remediation/Remediate-MdmEnrollmentBlock.ps1 diff --git a/Remediate-NoAutoUpdate.ps1 b/ProactiveRemediation/Remediation/Remediate-NoAutoUpdate.ps1 similarity index 100% rename from Remediate-NoAutoUpdate.ps1 rename to ProactiveRemediation/Remediation/Remediate-NoAutoUpdate.ps1 diff --git a/Remediate-SCCMClientAgent.ps1 b/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 similarity index 100% rename from Remediate-SCCMClientAgent.ps1 rename to ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 diff --git a/Remediate-TimeoutWarning.ps1 b/ProactiveRemediation/Remediation/Remediate-TimeoutWarning.ps1 similarity index 100% rename from Remediate-TimeoutWarning.ps1 rename to ProactiveRemediation/Remediation/Remediate-TimeoutWarning.ps1 diff --git a/Remediate-WSUS_LastOnlineScan.ps1 b/ProactiveRemediation/Remediation/Remediate-WSUS_LastOnlineScan.ps1 similarity index 100% rename from Remediate-WSUS_LastOnlineScan.ps1 rename to ProactiveRemediation/Remediation/Remediate-WSUS_LastOnlineScan.ps1 diff --git a/Remediate-WUEntries.ps1 b/ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 similarity index 100% rename from Remediate-WUEntries.ps1 rename to ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 diff --git a/Remediate-WUServer.ps1 b/ProactiveRemediation/Remediation/Remediate-WUServer.ps1 similarity index 100% rename from Remediate-WUServer.ps1 rename to ProactiveRemediation/Remediation/Remediate-WUServer.ps1 diff --git a/Remediate-WingetUpdates.ps1 b/ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 similarity index 100% rename from Remediate-WingetUpdates.ps1 rename to ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 diff --git a/Remediate-WiredAutoConfig.ps1 b/ProactiveRemediation/Remediation/Remediate-WiredAutoConfig.ps1 similarity index 100% rename from Remediate-WiredAutoConfig.ps1 rename to ProactiveRemediation/Remediation/Remediate-WiredAutoConfig.ps1 diff --git a/Backup-Permissions.ps1 b/Utilities/Backup-Permissions.ps1 similarity index 100% rename from Backup-Permissions.ps1 rename to Utilities/Backup-Permissions.ps1 diff --git a/Change-Ownership.ps1 b/Utilities/Change-Ownership.ps1 similarity index 100% rename from Change-Ownership.ps1 rename to Utilities/Change-Ownership.ps1 diff --git a/Create-100Files.ps1 b/Utilities/Create-100Files.ps1 similarity index 100% rename from Create-100Files.ps1 rename to Utilities/Create-100Files.ps1 diff --git a/Discover-Printers_CIM_WMI.ps1 b/Utilities/Discover-Printers_CIM_WMI.ps1 similarity index 100% rename from Discover-Printers_CIM_WMI.ps1 rename to Utilities/Discover-Printers_CIM_WMI.ps1 diff --git a/Discover-Printers_PS.ps1 b/Utilities/Discover-Printers_PS.ps1 similarity index 100% rename from Discover-Printers_PS.ps1 rename to Utilities/Discover-Printers_PS.ps1 diff --git a/Exchange-MailboxRetentionPolicyManager.ps1 b/Utilities/Exchange-MailboxRetentionPolicyManager.ps1 similarity index 100% rename from Exchange-MailboxRetentionPolicyManager.ps1 rename to Utilities/Exchange-MailboxRetentionPolicyManager.ps1 diff --git a/Get-Counts.ps1 b/Utilities/Get-Counts.ps1 similarity index 100% rename from Get-Counts.ps1 rename to Utilities/Get-Counts.ps1 diff --git a/Install-NotepadPlusPlus.ps1 b/Utilities/Install-NotepadPlusPlus.ps1 similarity index 100% rename from Install-NotepadPlusPlus.ps1 rename to Utilities/Install-NotepadPlusPlus.ps1 diff --git a/Check-AU.ps1 b/WindowsUpdate/Check-AU.ps1 similarity index 100% rename from Check-AU.ps1 rename to WindowsUpdate/Check-AU.ps1 diff --git a/Check-DisableDualScan.ps1 b/WindowsUpdate/Check-DisableDualScan.ps1 similarity index 100% rename from Check-DisableDualScan.ps1 rename to WindowsUpdate/Check-DisableDualScan.ps1 diff --git a/Check-NoAutoUpdate.ps1 b/WindowsUpdate/Check-NoAutoUpdate.ps1 similarity index 100% rename from Check-NoAutoUpdate.ps1 rename to WindowsUpdate/Check-NoAutoUpdate.ps1 diff --git a/Check-WSUS_LastOnlineScan.ps1 b/WindowsUpdate/Check-WSUS_LastOnlineScan.ps1 similarity index 100% rename from Check-WSUS_LastOnlineScan.ps1 rename to WindowsUpdate/Check-WSUS_LastOnlineScan.ps1 diff --git a/WindowsUpdate-Reset.ps1 b/WindowsUpdate/WindowsUpdate-Reset.ps1 similarity index 100% rename from WindowsUpdate-Reset.ps1 rename to WindowsUpdate/WindowsUpdate-Reset.ps1 From 9fdd64440b6251c05faeb80d0f0945f36a0eb986 Mon Sep 17 00:00:00 2001 From: Hal Date: Thu, 2 Apr 2026 13:55:42 -0400 Subject: [PATCH 101/107] Improve SCCM client agent detection and remediation scripts Rewrote both scripts from minimal single-check versions to comprehensive artifact removal: services, registry keys (x64 + WOW6432), directories, WMI namespaces, and SMSCFG.ini. Adds logging to IME log directory and post-removal verification. Useful for SCCM decommission or Intune-only migration scenarios. Co-Authored-By: Additional tools --- .../Detection/Detect-SCCMClientAgent.ps1 | 110 +++++++++-- .../Remediation/Remediate-SCCMClientAgent.ps1 | 185 ++++++++++++++++-- 2 files changed, 269 insertions(+), 26 deletions(-) diff --git a/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 b/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 index cee497a..287a2cf 100644 --- a/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 +++ b/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 @@ -1,12 +1,98 @@ -# Detection script for Intune Proactive Remediation - -# Path to the SCCM client agent executable -$SCCMClientExecutable = "C:\Windows\CCM\CcmExec.exe" - -if (Test-Path -Path $SCCMClientExecutable) { - Write-Output "SCCM client agent executable detected." - exit 1 -} else { - Write-Output "SCCM client agent executable not detected." - exit 0 -} +<# +.SYNOPSIS + Detects orphaned SCCM/ConfigMgr client agent installations. + +.DESCRIPTION + Intune Proactive Remediation - Detection Script + + Checks for SCCM client artifacts (services, registry keys, directories, installed + programs) that may remain after SCCM infrastructure has been decommissioned or + when devices are being migrated away from SCCM/co-management to Intune-only. + + Useful when: + - Migrating from SCCM co-management to Intune-only management + - SCCM infrastructure has been retired but agents remain on endpoints + - Cleaning up endpoints before deploying a new RMM or management agent + + Exit Codes: + - 0 = Compliant (no SCCM agent artifacts found) + - 1 = Non-compliant (SCCM artifacts detected — remediation required) + +.NOTES + Author: Hal Kurz + Version: 2.0 + Updated: 2026-04-02 + Repo: https://github.com/Hal-Scriptville/PowerShell +#> + +$ErrorActionPreference = "SilentlyContinue" + +$found = $false +$reasons = @() + +# 1. CCMExec service (primary indicator) +$svc = Get-Service -Name "CcmExec" -ErrorAction SilentlyContinue +if ($svc) { + $found = $true + $reasons += "CcmExec service present (Status: $($svc.Status), StartType: $($svc.StartType))" +} + +# 2. SMS Task Sequence Manager service +$smsTsm = Get-Service -Name "smstsmgr" -ErrorAction SilentlyContinue +if ($smsTsm) { + $found = $true + $reasons += "smstsmgr (SMS Task Sequence Manager) service present" +} + +# 3. CCMSetup installer service +$ccmSetupSvc = Get-Service -Name "ccmsetup" -ErrorAction SilentlyContinue +if ($ccmSetupSvc) { + $found = $true + $reasons += "ccmsetup service present" +} + +# 4. CCM registry key +if (Test-Path "HKLM:\SOFTWARE\Microsoft\CCM") { + $found = $true + $reasons += "SCCM registry key present: HKLM:\SOFTWARE\Microsoft\CCM" +} + +# 5. CCM install directory +if (Test-Path "$env:SystemRoot\CCM") { + $found = $true + $reasons += "CCM directory present: $env:SystemRoot\CCM" +} + +# 6. CCMSetup directory +if (Test-Path "$env:SystemRoot\ccmsetup") { + $found = $true + $reasons += "CCMSetup directory present: $env:SystemRoot\ccmsetup" +} + +# 7. SCCM client in Add/Remove Programs +$uninstallPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" +) +foreach ($path in $uninstallPaths) { + if (Test-Path $path) { + Get-ChildItem -Path $path -ErrorAction SilentlyContinue | ForEach-Object { + $name = (Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue).DisplayName + if ($name -like "*Configuration Manager Client*" -or $name -like "*SMS Client*") { + $found = $true + $reasons += "SCCM client in installed programs: $name" + } + } + } +} + +# Output and exit +if ($found) { + Write-Output "NON-COMPLIANT: SCCM agent artifacts detected" + $reasons | ForEach-Object { Write-Output " - $_" } + exit 1 +} +else { + Write-Output "COMPLIANT: No SCCM agent artifacts found" + exit 0 +} diff --git a/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 b/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 index c9a5f0a..9490809 100644 --- a/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 +++ b/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 @@ -1,14 +1,171 @@ -# Remediation script for Intune Proactive Remediation - -# Path to the SCCM client agent executable and uninstall program -$SCCMClientExecutable = "C:\Windows\CCM\CcmExec.exe" -$UninstallCommand = "C:\Windows\CCMSetup\CCMSetup.exe /uninstall" - -# Check if the SCCM client agent executable is present -if (Test-Path -Path $SCCMClientExecutable) { - Write-Output "SCCM client agent executable detected. Initiating uninstall process." - Start-Process -FilePath "cmd.exe" -ArgumentList "/c", $UninstallCommand -Wait -NoNewWindow - Write-Output "SCCM client agent uninstall process completed." -} else { - Write-Output "SCCM client agent executable not detected. No action needed." -} +<# +.SYNOPSIS + Removes orphaned SCCM/ConfigMgr client agent installations. + +.DESCRIPTION + Intune Proactive Remediation - Remediation Script + + Removes SCCM client artifacts from endpoints where SCCM infrastructure has been + decommissioned or devices are being migrated to Intune-only management. + + Removal sequence: + 1. Stop and disable CcmExec and related services + 2. Run ccmsetup.exe /uninstall (official path) if binary is present + 3. Delete services from Service Control Manager + 4. Remove registry keys (CCM, CCMSetup, SMS — x64 and WOW6432Node) + 5. Remove directories (CCM, ccmsetup, ccmcache, SMSCFG.ini) + 6. Remove WMI namespaces (root\CCM, root\SMS) + 7. Verify and log + + Log: C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\RemoveSCCMAgent.log + + Exit Codes: + - 0 = Remediation complete (reboot may be required for full cleanup) + - 1 = Remediation failed + +.NOTES + Author: Hal Kurz + Version: 2.0 + Updated: 2026-04-02 + Repo: https://github.com/Hal-Scriptville/PowerShell + + WARNING: Only use when SCCM infrastructure is decommissioned or devices are + being intentionally removed from SCCM management. This permanently removes + the SCCM client — the device will no longer check in to any SCCM site. +#> + +$ErrorActionPreference = "SilentlyContinue" + +$logPath = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\RemoveSCCMAgent.log" + +function Write-Log { + param([string]$Message) + $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $line = "[$ts] $Message" + Add-Content -Path $logPath -Value $line -Force + Write-Output $line +} + +Write-Log "=== Starting SCCM Agent Removal ===" + +# --- Step 1: Stop services --- +Write-Log "Step 1: Stopping SCCM services..." + +@("CcmExec", "smstsmgr", "ccmsetup", "CmRcService") | ForEach-Object { + $svc = Get-Service -Name $_ -ErrorAction SilentlyContinue + if ($svc) { + Write-Log " Stopping $_ (state: $($svc.Status))..." + Stop-Service -Name $_ -Force -ErrorAction SilentlyContinue + Set-Service -Name $_ -StartupType Disabled -ErrorAction SilentlyContinue + Write-Log " $_ stopped and disabled" + } +} + +# --- Step 2: Run official uninstaller --- +Write-Log "Step 2: Looking for ccmsetup.exe uninstaller..." + +$ccmSetupPaths = @( + "$env:SystemRoot\ccmsetup\ccmsetup.exe", + "$env:SystemRoot\CCM\ccmsetup.exe" +) + +$uninstallerRan = $false +foreach ($path in $ccmSetupPaths) { + if (Test-Path $path) { + Write-Log " Found: $path — running /uninstall" + $proc = Start-Process -FilePath $path -ArgumentList "/uninstall" -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue + Write-Log " Exit code: $($proc.ExitCode)" + $uninstallerRan = $true + Start-Sleep -Seconds 30 + break + } +} + +if (-not $uninstallerRan) { + Write-Log " ccmsetup.exe not found — proceeding with manual cleanup" +} + +# --- Step 3: Remove services from SCM --- +Write-Log "Step 3: Removing services from SCM..." + +@("CcmExec", "smstsmgr", "ccmsetup", "CmRcService", "CCMSetupService") | ForEach-Object { + if (Get-Service -Name $_ -ErrorAction SilentlyContinue) { + sc.exe delete $_ | Out-Null + Write-Log " Deleted service: $_" + } +} + +# --- Step 4: Remove registry keys --- +Write-Log "Step 4: Removing registry keys..." + +@( + "HKLM:\SOFTWARE\Microsoft\CCM", + "HKLM:\SOFTWARE\Microsoft\CCMSetup", + "HKLM:\SOFTWARE\Microsoft\SMS", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\CCM", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\CCMSetup", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\SMS", + "HKLM:\SYSTEM\CurrentControlSet\Services\CcmExec", + "HKLM:\SYSTEM\CurrentControlSet\Services\smstsmgr", + "HKLM:\SYSTEM\CurrentControlSet\Services\ccmsetup" +) | ForEach-Object { + if (Test-Path $_) { + Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue + Write-Log " Removed: $_" + } +} + +# --- Step 5: Remove directories and files --- +Write-Log "Step 5: Removing directories and files..." + +@( + "$env:SystemRoot\CCM", + "$env:SystemRoot\ccmsetup", + "$env:SystemRoot\ccmcache", + "$env:ProgramData\Microsoft Configuration Manager" +) | ForEach-Object { + if (Test-Path $_) { + Remove-Item -Path $_ -Recurse -Force -ErrorAction SilentlyContinue + Write-Log " Removed: $_" + } +} + +# SMSCFG.ini stores the site assignment — important to remove +$smsCfg = "$env:SystemRoot\SMSCFG.ini" +if (Test-Path $smsCfg) { + Remove-Item -Path $smsCfg -Force -ErrorAction SilentlyContinue + Write-Log " Removed SMSCFG.ini (site assignment file)" +} + +# --- Step 6: Remove WMI namespaces --- +Write-Log "Step 6: Removing WMI namespaces..." + +try { + $ccmNS = Get-WmiObject -Query "SELECT * FROM __Namespace WHERE Name='CCM'" -Namespace "root" -ErrorAction SilentlyContinue + if ($ccmNS) { $ccmNS.Delete(); Write-Log " Removed root\CCM WMI namespace" } + + $smsNS = Get-WmiObject -Query "SELECT * FROM __Namespace WHERE Name='SMS'" -Namespace "root" -ErrorAction SilentlyContinue + if ($smsNS) { $smsNS.Delete(); Write-Log " Removed root\SMS WMI namespace" } +} +catch { + Write-Log " WMI removal error (non-fatal): $($_.Exception.Message)" +} + +# --- Step 7: Verify --- +Write-Log "Step 7: Verifying removal..." + +$remaining = @() +if ((Get-Service -Name "CcmExec" -ErrorAction SilentlyContinue)?.Status -ne 'Stopped') { $remaining += "CcmExec service" } +if (Test-Path "HKLM:\SOFTWARE\Microsoft\CCM") { $remaining += "CCM registry key" } +if (Test-Path "$env:SystemRoot\CCM\CcmExec.exe") { $remaining += "CcmExec.exe binary" } + +if ($remaining.Count -eq 0) { + Write-Log "Verification PASSED — all SCCM artifacts removed" +} +else { + Write-Log "Verification NOTE — remaining items (may clear after reboot):" + $remaining | ForEach-Object { Write-Log " - $_" } +} + +Write-Log "=== SCCM Agent Removal Complete ===" +exit 0 From a4d27694edbcf32fa36efda2b78633614a6c49b0 Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 09:13:29 -0400 Subject: [PATCH 102/107] Rewrite Build-IntuneAppBuilder as parameterized packager v1 only scaffolded folders and downloaded IntuneWinAppUtil.exe. v2 produces a ready-to-edit package and can build the .intunewin. Changes: - param() block: -AppName, -BasePath, -SetupFile, -Build, -Force, -UseCurl - TLS 1.2 + ErrorActionPreference='Stop' - Idempotent download; prints SHA256 of the tool - -UseCurl switch for EDR-blocked environments (FortiEDR Invoke-WebRequest block) - Stub scripts seeded: install.ps1, uninstall.ps1, detect.ps1 with Intune-standard log paths and exit-code conventions - Existing stubs preserved on re-run unless -Force - -Build flag invokes IntuneWinAppUtil to produce the .intunewin Co-Authored-By: Claude --- Intune/Build-IntuneAppBuilder.ps1 | 238 +++++++++++++++++++++++++----- 1 file changed, 201 insertions(+), 37 deletions(-) diff --git a/Intune/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 index 1fbdf3c..6eb5f40 100644 --- a/Intune/Build-IntuneAppBuilder.ps1 +++ b/Intune/Build-IntuneAppBuilder.ps1 @@ -1,37 +1,201 @@ -# Define the base path (current location or customize) -$basePath = Join-Path -Path $PSScriptRoot -ChildPath "App" - -# Define folder structure -$folders = @( - $basePath, - "$basePath\Intune", - "$basePath\Source", - "$basePath\Detection", - "$basePath\Output" -) - -# Create folders if they don't exist -foreach ($folder in $folders) { - if (-not (Test-Path -Path $folder)) { - New-Item -Path $folder -ItemType Directory | Out-Null - Write-Host "Created folder: $folder" - } else { - Write-Host "Folder already exists: $folder" - } -} - -# Updated correct binary download URL -$downloadUrl = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" -$destination = Join-Path -Path $basePath -ChildPath "IntuneWinAppUtil.exe" - -try { - Write-Host "Downloading IntuneWinAppUtil.exe..." - Invoke-WebRequest -Uri $downloadUrl -OutFile $destination -UseBasicParsing - Write-Host "Download complete: $destination" - - # Unblock the file - Unblock-File -Path $destination - Write-Host "File unblocked." -} catch { - Write-Warning "Failed to download IntuneWinAppUtil.exe: $_" -} +<# +.SYNOPSIS + Scaffolds an Intune Win32 app package and optionally builds the .intunewin file. + +.DESCRIPTION + Creates the standard folder structure (Source, Detection, Output, Intune), + downloads IntuneWinAppUtil.exe, seeds install/uninstall/detect script stubs, + and (optionally) invokes the Content Prep Tool to produce the .intunewin. + +.PARAMETER AppName + Name of the app being packaged. Drives folder naming. Default: "App". + +.PARAMETER BasePath + Root directory where the package scaffold is created. + Default: \ + +.PARAMETER SetupFile + Name of the install script inside Source\ (passed to -s flag of IntuneWinAppUtil). + Default: install.ps1 + +.PARAMETER Build + If specified, invokes IntuneWinAppUtil.exe after scaffolding to produce the .intunewin. + +.PARAMETER Force + Overwrite existing stub scripts. By default, existing files are preserved. + +.PARAMETER UseCurl + Use curl.exe for the IntuneWinAppUtil.exe download instead of Invoke-WebRequest. + Useful when EDR (e.g., FortiEDR) blocks PowerShell web requests. + +.EXAMPLE + .\Build-IntuneAppBuilder.ps1 -AppName "NinjaOne" -Build + +.EXAMPLE + .\Build-IntuneAppBuilder.ps1 -AppName "NinjaOne" -UseCurl -Build + +.NOTES + Stub scripts are templates only. Edit install.ps1, uninstall.ps1, and detect.ps1 + before running -Build for a real deployment. +#> +[CmdletBinding()] +param( + [string]$AppName = "App", + [string]$BasePath = (Join-Path -Path $PSScriptRoot -ChildPath $AppName), + [string]$SetupFile = "install.ps1", + [switch]$Build, + [switch]$Force, + [switch]$UseCurl +) + +$ErrorActionPreference = 'Stop' +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# ---- Folder scaffold ------------------------------------------------------- +$folders = @( + $BasePath, + (Join-Path $BasePath "Intune"), + (Join-Path $BasePath "Source"), + (Join-Path $BasePath "Detection"), + (Join-Path $BasePath "Output") +) + +foreach ($folder in $folders) { + if (-not (Test-Path $folder)) { + New-Item -Path $folder -ItemType Directory | Out-Null + Write-Host "Created: $folder" + } else { + Write-Host "Exists: $folder" + } +} + +# ---- IntuneWinAppUtil.exe download ---------------------------------------- +$toolUrl = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" +$toolPath = Join-Path $BasePath "IntuneWinAppUtil.exe" + +if ((Test-Path $toolPath) -and -not $Force) { + Write-Host "Tool already present: $toolPath (use -Force to re-download)" +} else { + Write-Host "Downloading IntuneWinAppUtil.exe..." + try { + if ($UseCurl) { + $curl = Get-Command curl.exe -ErrorAction Stop + & $curl.Source -sSL -o $toolPath $toolUrl + if ($LASTEXITCODE -ne 0) { throw "curl exit $LASTEXITCODE" } + } else { + Invoke-WebRequest -Uri $toolUrl -OutFile $toolPath -UseBasicParsing + } + Unblock-File -Path $toolPath + $hash = (Get-FileHash -Path $toolPath -Algorithm SHA256).Hash + Write-Host "Download complete." + Write-Host "SHA256: $hash" + } catch { + throw "Failed to download IntuneWinAppUtil.exe: $_" + } +} + +# ---- Stub scripts --------------------------------------------------------- +$installStub = @' +# install.ps1 - Intune Win32 app install script +# Runs as SYSTEM under Intune Management Extension. +# Log to C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\ for visibility. + +$ErrorActionPreference = 'Stop' +$log = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\-install.log" +Start-Transcript -Path $log -Append + +try { + # TODO: Replace with actual install command + # Example (MSI): + # Start-Process msiexec.exe -ArgumentList '/i','installer.msi','/qn','/norestart' -Wait + # Example (EXE): + # Start-Process .\setup.exe -ArgumentList '/S' -Wait + Write-Host "Install placeholder - edit install.ps1" + exit 0 +} catch { + Write-Error $_ + exit 1 +} finally { + Stop-Transcript +} +'@ + +$uninstallStub = @' +# uninstall.ps1 - Intune Win32 app uninstall script +$ErrorActionPreference = 'Stop' +$log = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\-uninstall.log" +Start-Transcript -Path $log -Append + +try { + # TODO: Replace with actual uninstall command + Write-Host "Uninstall placeholder - edit uninstall.ps1" + exit 0 +} catch { + Write-Error $_ + exit 1 +} finally { + Stop-Transcript +} +'@ + +$detectStub = @' +# detect.ps1 - Intune Win32 app detection script +# Intune considers the app installed if this script: +# - Exits 0 AND writes output to STDOUT. +# Exit 0 with no output = not installed. + +# TODO: Replace with real detection (registry key, file path, service, or version check). +# Example - service presence: +# if (Get-Service -Name 'NinjaRMMAgent' -ErrorAction SilentlyContinue) { +# Write-Output "Installed"; exit 0 +# } +# exit 0 + +exit 0 +'@ + +$stubs = @{ + (Join-Path $BasePath "Source\$SetupFile") = $installStub + (Join-Path $BasePath "Source\uninstall.ps1") = $uninstallStub + (Join-Path $BasePath "Detection\detect.ps1") = $detectStub +} + +foreach ($path in $stubs.Keys) { + if ((Test-Path $path) -and -not $Force) { + Write-Host "Stub exists (preserved): $path" + } else { + $stubs[$path] -replace '', $AppName | Set-Content -Path $path -Encoding UTF8 + Write-Host "Wrote stub: $path" + } +} + +# ---- Build .intunewin ------------------------------------------------------ +if ($Build) { + $sourceDir = Join-Path $BasePath "Source" + $setupPath = Join-Path $sourceDir $SetupFile + $outputDir = Join-Path $BasePath "Output" + + if (-not (Test-Path $setupPath)) { + throw "Setup file not found: $setupPath. Edit $SetupFile before building." + } + + Write-Host "" + Write-Host "Building .intunewin..." + & $toolPath -c $sourceDir -s $SetupFile -o $outputDir -q + if ($LASTEXITCODE -ne 0) { + throw "IntuneWinAppUtil failed with exit code $LASTEXITCODE" + } + + $pkg = Get-ChildItem -Path $outputDir -Filter *.intunewin | Select-Object -First 1 + if ($pkg) { + Write-Host "Build complete: $($pkg.FullName)" + } else { + Write-Warning "Build reported success but no .intunewin found in $outputDir" + } +} else { + Write-Host "" + Write-Host "Scaffold complete. Next steps:" + Write-Host " 1. Drop installer binary into: $BasePath\Source" + Write-Host " 2. Edit $SetupFile, uninstall.ps1, and Detection\detect.ps1" + Write-Host " 3. Re-run with -Build to produce the .intunewin" +} From 4a3591352a7781918eac73e9a71a14cb6fce5e68 Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 09:25:04 -0400 Subject: [PATCH 103/107] Use Chrome as the public reference example (replaces NinjaOne) - Swap .EXAMPLE blocks in Build-IntuneAppBuilder.ps1 from NinjaOne to Chrome - Add Examples/Chrome/ with a complete reference package: install.ps1, uninstall.ps1, detect.ps1, README.md - Chrome chosen because it's ubiquitous and has well-known MSI + detection patterns. NinjaOne and other client-specific packages live in private client-script repositories. Co-Authored-By: Claude --- Intune/Build-IntuneAppBuilder.ps1 | 6 +++-- Intune/Examples/Chrome/README.md | 33 ++++++++++++++++++++++++++++ Intune/Examples/Chrome/detect.ps1 | 23 +++++++++++++++++++ Intune/Examples/Chrome/install.ps1 | 26 ++++++++++++++++++++++ Intune/Examples/Chrome/uninstall.ps1 | 27 +++++++++++++++++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 Intune/Examples/Chrome/README.md create mode 100644 Intune/Examples/Chrome/detect.ps1 create mode 100644 Intune/Examples/Chrome/install.ps1 create mode 100644 Intune/Examples/Chrome/uninstall.ps1 diff --git a/Intune/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 index 6eb5f40..d836a76 100644 --- a/Intune/Build-IntuneAppBuilder.ps1 +++ b/Intune/Build-IntuneAppBuilder.ps1 @@ -29,14 +29,16 @@ Useful when EDR (e.g., FortiEDR) blocks PowerShell web requests. .EXAMPLE - .\Build-IntuneAppBuilder.ps1 -AppName "NinjaOne" -Build + .\Build-IntuneAppBuilder.ps1 -AppName "Chrome" -Build .EXAMPLE - .\Build-IntuneAppBuilder.ps1 -AppName "NinjaOne" -UseCurl -Build + .\Build-IntuneAppBuilder.ps1 -AppName "Chrome" -UseCurl -Build .NOTES Stub scripts are templates only. Edit install.ps1, uninstall.ps1, and detect.ps1 before running -Build for a real deployment. + + See Examples\Chrome\ for a complete reference package. #> [CmdletBinding()] param( diff --git a/Intune/Examples/Chrome/README.md b/Intune/Examples/Chrome/README.md new file mode 100644 index 0000000..2b80ab8 --- /dev/null +++ b/Intune/Examples/Chrome/README.md @@ -0,0 +1,33 @@ +# Example: Google Chrome Enterprise (Intune Win32 App) + +Reference package showing a complete Win32 app built with `Build-IntuneAppBuilder.ps1`. + +## Files + +| File | Purpose | +|------|---------| +| `install.ps1` | Silent install via msiexec, logs to IME log dir | +| `uninstall.ps1` | Uninstall by product code | +| `detect.ps1` | Version-based detection on chrome.exe | + +## Build + +1. Download `googlechromestandaloneenterprise64.msi` from https://chromeenterprise.google/download/ +2. Place it alongside `install.ps1` (same `Source\` folder) +3. From the package root: + ```powershell + .\Build-IntuneAppBuilder.ps1 -AppName Chrome -Build + ``` +4. Upload `Output\install.intunewin` to Intune. + +## Intune app settings + +- **Install command:** `powershell.exe -ExecutionPolicy Bypass -File install.ps1` +- **Uninstall command:** `powershell.exe -ExecutionPolicy Bypass -File uninstall.ps1` +- **Install behavior:** System +- **Detection rule:** Custom script → upload `detect.ps1`, run as 32-bit: No + +## Notes + +- Replace `{CHROME-PRODUCT-CODE-GUID}` in `uninstall.ps1` with the actual product code of the MSI you packaged. +- `detect.ps1` uses version >= 120. Update `$minVersion` to gate on a specific baseline. diff --git a/Intune/Examples/Chrome/detect.ps1 b/Intune/Examples/Chrome/detect.ps1 new file mode 100644 index 0000000..5867a87 --- /dev/null +++ b/Intune/Examples/Chrome/detect.ps1 @@ -0,0 +1,23 @@ +# detect.ps1 - Google Chrome detection for Intune Win32 app. +# Intune rule: exit 0 + STDOUT = installed; exit 0 + no output = not installed. +# +# Detection strategy: version check against chrome.exe. Change $minVersion to +# whatever minimum you want the Win32 app to treat as "installed". + +$minVersion = [Version]'120.0.0.0' +$chromePaths = @( + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" +) + +foreach ($path in $chromePaths) { + if (Test-Path $path) { + $version = [Version](Get-Item $path).VersionInfo.FileVersion + if ($version -ge $minVersion) { + Write-Output "Chrome $version detected at $path" + exit 0 + } + } +} + +exit 0 diff --git a/Intune/Examples/Chrome/install.ps1 b/Intune/Examples/Chrome/install.ps1 new file mode 100644 index 0000000..2497644 --- /dev/null +++ b/Intune/Examples/Chrome/install.ps1 @@ -0,0 +1,26 @@ +# install.ps1 - Google Chrome Enterprise install (Intune Win32 app) +# Runs as SYSTEM under Intune Management Extension. + +$ErrorActionPreference = 'Stop' +$log = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\Chrome-install.log" +Start-Transcript -Path $log -Append + +try { + $msi = Join-Path $PSScriptRoot "googlechromestandaloneenterprise64.msi" + if (-not (Test-Path $msi)) { throw "MSI not found: $msi" } + + $args = @('/i', "`"$msi`"", '/qn', '/norestart', 'REBOOT=ReallySuppress') + $proc = Start-Process -FilePath msiexec.exe -ArgumentList $args -Wait -PassThru + + if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { + throw "msiexec failed with exit code $($proc.ExitCode)" + } + + Write-Host "Chrome install complete (exit $($proc.ExitCode))" + exit 0 +} catch { + Write-Error $_ + exit 1 +} finally { + Stop-Transcript +} diff --git a/Intune/Examples/Chrome/uninstall.ps1 b/Intune/Examples/Chrome/uninstall.ps1 new file mode 100644 index 0000000..085f4fb --- /dev/null +++ b/Intune/Examples/Chrome/uninstall.ps1 @@ -0,0 +1,27 @@ +# uninstall.ps1 - Google Chrome Enterprise uninstall (Intune Win32 app) + +$ErrorActionPreference = 'Stop' +$log = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\Chrome-uninstall.log" +Start-Transcript -Path $log -Append + +try { + # Product code for Google Chrome Enterprise x64 (stable channel). + # Verify against target MSI version before trusting in production: + # Get-Package | Where-Object Name -like 'Google Chrome*' | Select-Object -ExpandProperty PackageFilename + $productCode = '{CHROME-PRODUCT-CODE-GUID}' + + $args = @('/x', $productCode, '/qn', '/norestart') + $proc = Start-Process -FilePath msiexec.exe -ArgumentList $args -Wait -PassThru + + if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010 -and $proc.ExitCode -ne 1605) { + throw "msiexec failed with exit code $($proc.ExitCode)" + } + + Write-Host "Chrome uninstall complete (exit $($proc.ExitCode))" + exit 0 +} catch { + Write-Error $_ + exit 1 +} finally { + Stop-Transcript +} From 3e600e4b30a1358ad0de8bb3ca698c5c26e51ae8 Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 10:40:41 -0400 Subject: [PATCH 104/107] Add -ToolPath param to reuse a shared IntuneWinAppUtil.exe Previously every app scaffold downloaded its own copy of the Content Prep Tool. -ToolPath lets one shared tool (e.g., C:\Build\Intune\IntuneWinAppUtil.exe) be referenced by every app, avoiding redundant downloads and simplifying versioning of the tool across a build host. Behavior: - When -ToolPath is set, the tool is validated and reused from the given path. - When -ToolPath is omitted, existing behavior is preserved (per-app download). Co-Authored-By: Claude --- Intune/Build-IntuneAppBuilder.ps1 | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Intune/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 index d836a76..97daa0b 100644 --- a/Intune/Build-IntuneAppBuilder.ps1 +++ b/Intune/Build-IntuneAppBuilder.ps1 @@ -28,12 +28,22 @@ Use curl.exe for the IntuneWinAppUtil.exe download instead of Invoke-WebRequest. Useful when EDR (e.g., FortiEDR) blocks PowerShell web requests. +.PARAMETER ToolPath + Absolute path to an existing IntuneWinAppUtil.exe. When set, the tool is + reused from this location and NOT downloaded into the app folder. + Typical use: one shared tool under C:\Build\Intune\IntuneWinAppUtil.exe + referenced by every app scaffold. + .EXAMPLE .\Build-IntuneAppBuilder.ps1 -AppName "Chrome" -Build .EXAMPLE .\Build-IntuneAppBuilder.ps1 -AppName "Chrome" -UseCurl -Build +.EXAMPLE + .\Build-IntuneAppBuilder.ps1 -AppName "Chrome" ` + -ToolPath "C:\Build\Intune\IntuneWinAppUtil.exe" -Build + .NOTES Stub scripts are templates only. Edit install.ps1, uninstall.ps1, and detect.ps1 before running -Build for a real deployment. @@ -45,6 +55,7 @@ param( [string]$AppName = "App", [string]$BasePath = (Join-Path -Path $PSScriptRoot -ChildPath $AppName), [string]$SetupFile = "install.ps1", + [string]$ToolPath, [switch]$Build, [switch]$Force, [switch]$UseCurl @@ -71,11 +82,22 @@ foreach ($folder in $folders) { } } -# ---- IntuneWinAppUtil.exe download ---------------------------------------- +# ---- IntuneWinAppUtil.exe resolution --------------------------------------- $toolUrl = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" -$toolPath = Join-Path $BasePath "IntuneWinAppUtil.exe" -if ((Test-Path $toolPath) -and -not $Force) { +if ($ToolPath) { + if (-not (Test-Path $ToolPath)) { + throw "ToolPath does not exist: $ToolPath" + } + $toolPath = (Resolve-Path $ToolPath).Path + Write-Host "Using shared tool: $toolPath" +} else { + $toolPath = Join-Path $BasePath "IntuneWinAppUtil.exe" +} + +if ($ToolPath) { + # Shared tool path provided — skip download logic entirely. +} elseif ((Test-Path $toolPath) -and -not $Force) { Write-Host "Tool already present: $toolPath (use -Force to re-download)" } else { Write-Host "Downloading IntuneWinAppUtil.exe..." From 70375255040778ea69486f18a5f7b7f5ebb36368 Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 10:47:02 -0400 Subject: [PATCH 105/107] =?UTF-8?q?Add=20New-IntuneApp.ps1=20=E2=80=94=20l?= =?UTF-8?q?ightweight=20scaffold=20for=20the=20shared-root=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to Build-IntuneAppBuilder.ps1. Creates a per-app folder under a shared build root (default C:\Build\Intune) with Source/Detection/Output, and downloads IntuneWinAppUtil.exe once at the root so every app reuses it. Prints the exact command to build the .intunewin so the next step is copy-pasteable after dropping in scripts and binaries. Co-Authored-By: Claude --- Intune/New-IntuneApp.ps1 | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Intune/New-IntuneApp.ps1 diff --git a/Intune/New-IntuneApp.ps1 b/Intune/New-IntuneApp.ps1 new file mode 100644 index 0000000..5e9352d --- /dev/null +++ b/Intune/New-IntuneApp.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS + Scaffolds a new Intune Win32 app folder under a shared build root. + +.DESCRIPTION + Creates \\{Source,Detection,Output} and, on first + use, downloads IntuneWinAppUtil.exe to so it is shared by + every app in that root. Prints next-step commands. + +.PARAMETER AppName + Name of the app (drives folder naming). Required. + +.PARAMETER BuildRoot + Shared build directory. Default: C:\Build\Intune + +.PARAMETER UseCurl + Use curl.exe for the tool download (EDR-friendly fallback). + +.EXAMPLE + .\New-IntuneApp.ps1 -AppName Chrome + +.EXAMPLE + .\New-IntuneApp.ps1 -AppName JavaPin -BuildRoot D:\Packaging -UseCurl +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)][string]$AppName, + [string]$BuildRoot = "C:\Build\Intune", + [switch]$UseCurl +) + +$ErrorActionPreference = 'Stop' +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +$appRoot = Join-Path $BuildRoot $AppName +$folders = @( + $appRoot, + (Join-Path $appRoot "Source"), + (Join-Path $appRoot "Detection"), + (Join-Path $appRoot "Output") +) + +foreach ($f in $folders) { + if (-not (Test-Path $f)) { + New-Item -Path $f -ItemType Directory | Out-Null + Write-Host "Created: $f" + } else { + Write-Host "Exists: $f" + } +} + +# Shared IntuneWinAppUtil.exe at BuildRoot (one per build host, not per app) +$tool = Join-Path $BuildRoot "IntuneWinAppUtil.exe" +if (-not (Test-Path $tool)) { + $url = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" + Write-Host "Downloading IntuneWinAppUtil.exe to $tool..." + if ($UseCurl) { + & curl.exe -sSL -o $tool $url + if ($LASTEXITCODE -ne 0) { throw "curl exit $LASTEXITCODE" } + } else { + Invoke-WebRequest -Uri $url -OutFile $tool -UseBasicParsing + } + Unblock-File $tool + Write-Host "SHA256: $((Get-FileHash $tool -Algorithm SHA256).Hash)" +} else { + Write-Host "Tool (shared): $tool" +} + +Write-Host "" +Write-Host "Next steps:" +Write-Host " 1. Drop install.ps1 + binaries into: $appRoot\Source" +Write-Host " 2. Drop detect.ps1 into: $appRoot\Detection" +Write-Host " 3. Build the .intunewin:" +Write-Host " & `"$tool`" -c `"$appRoot\Source`" -s install.ps1 -o `"$appRoot\Output`" -q" +Write-Host " Or use Build-IntuneAppBuilder.ps1 with -ToolPath `"$tool`"" From 151417c7158f01ef9ea5191004c97ee3bb22d63f Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 11:45:07 -0400 Subject: [PATCH 106/107] Add version header and changelog (v3.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the v1 → v2 → v3 evolution inline so future readers can see what changed without digging through git history. Co-Authored-By: Claude --- Intune/Build-IntuneAppBuilder.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Intune/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 index 97daa0b..dcbe4f1 100644 --- a/Intune/Build-IntuneAppBuilder.ps1 +++ b/Intune/Build-IntuneAppBuilder.ps1 @@ -45,10 +45,23 @@ -ToolPath "C:\Build\Intune\IntuneWinAppUtil.exe" -Build .NOTES + Version: 3.0 Stub scripts are templates only. Edit install.ps1, uninstall.ps1, and detect.ps1 before running -Build for a real deployment. See Examples\Chrome\ for a complete reference package. + + Changelog: + 3.0 — Added -ToolPath to reuse a shared IntuneWinAppUtil.exe across apps. + Added companion New-IntuneApp.ps1 for lightweight per-app scaffolds + under a shared build root. + 2.0 — Parameterized (AppName, BasePath, SetupFile, UseCurl). + Added -Build to invoke IntuneWinAppUtil and produce .intunewin. + Added install/uninstall/detect stub scripts. + Idempotent tool download; prints SHA256. + Chrome reference package under Examples\Chrome\. + 1.0 — Original scaffolder: creates App\{Intune,Source,Detection,Output} + and downloads IntuneWinAppUtil.exe. #> [CmdletBinding()] param( From a0a3d6325dfb6c702c918fce94a321f478a882ba Mon Sep 17 00:00:00 2001 From: Hal Date: Tue, 14 Apr 2026 12:14:13 -0400 Subject: [PATCH 107/107] =?UTF-8?q?Add=20Build-TokenizedAgentPackages.ps1?= =?UTF-8?q?=20=E2=80=94=20bulk=20builder=20from=20CSV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic multi-location Win32 app builder for RMM agents with tokenized per-location MSI URLs (NinjaOne, Atera, Datto, etc.). For each row in an input CSV: - scaffolds Source/Detection/Output under an output root - downloads the per-location tokenized MSI - copies shared install/uninstall/detect templates - invokes IntuneWinAppUtil and renames the artifact for the location Filter column lets the same CSV hold all locations but only build Intune-target rows (e.g., skip AWS server-only locations that deploy via SSM). Co-Authored-By: Claude --- Intune/Build-TokenizedAgentPackages.ps1 | 178 ++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Intune/Build-TokenizedAgentPackages.ps1 diff --git a/Intune/Build-TokenizedAgentPackages.ps1 b/Intune/Build-TokenizedAgentPackages.ps1 new file mode 100644 index 0000000..776aed6 --- /dev/null +++ b/Intune/Build-TokenizedAgentPackages.ps1 @@ -0,0 +1,178 @@ +<# +.SYNOPSIS + Builds multiple Intune Win32 app packages from a CSV of tokenized RMM agent URLs. + +.DESCRIPTION + For each row in the input CSV, downloads a tokenized agent MSI, scaffolds a + Source/Detection/Output folder under an output root, copies shared template + scripts (install/uninstall/detect) into place, and invokes IntuneWinAppUtil + to produce a .intunewin package named for the location. + + Built for RMM agents with per-location tokenized download URLs + (NinjaOne, Atera, Datto, etc.). One CSV row = one .intunewin output. + +.PARAMETER CsvPath + CSV with at least these columns: + LocationName — friendly name (used in logs) + Slug — filename-safe slug used in the download URL + Token — per-location token/GUID in the URL + UrlTemplate — URL format with {TOKEN} and {SLUG} placeholders + DeployTarget — filter column (only rows matching -FilterValue are processed) + +.PARAMETER TemplatePath + Folder containing template scripts to seed each package. Expected layout: + {TemplatePath}\Source\install.ps1 + {TemplatePath}\Source\uninstall.ps1 + {TemplatePath}\Detection\detect.ps1 + +.PARAMETER OutputRoot + Build root. Each location is built under {OutputRoot}\{PackagePrefix}-{Slug}\. + Default: C:\Build\Intune + +.PARAMETER ToolPath + Path to IntuneWinAppUtil.exe. Required. + +.PARAMETER PackagePrefix + Prefix for per-location folders. Default: derived from template folder name. + +.PARAMETER FilterColumn / FilterValue + Only process rows where {FilterColumn} equals {FilterValue}. + Default: DeployTarget = intune + +.PARAMETER Force + Overwrite existing package folders/files. + +.PARAMETER UseCurl + Use curl.exe for MSI downloads (EDR-friendly). + +.EXAMPLE + .\Build-TokenizedAgentPackages.ps1 ` + -CsvPath "C:\Build\Client-Scripts-Presidio\HealthTrackRx\Scripts\Intune\NinjaOne\locations.csv" ` + -TemplatePath "C:\Build\Client-Scripts-Presidio\HealthTrackRx\Scripts\Intune\NinjaOne" ` + -OutputRoot "C:\Build\Intune" ` + -ToolPath "C:\Build\Intune\IntuneWinAppUtil.exe" ` + -PackagePrefix "NinjaOne-HealthTrackRx" + +.NOTES + Version: 1.0 +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)][string]$CsvPath, + [Parameter(Mandatory)][string]$TemplatePath, + [string]$OutputRoot = "C:\Build\Intune", + [Parameter(Mandatory)][string]$ToolPath, + [string]$PackagePrefix = (Split-Path $TemplatePath -Leaf), + [string]$FilterColumn = "DeployTarget", + [string]$FilterValue = "intune", + [switch]$Force, + [switch]$UseCurl +) + +$ErrorActionPreference = 'Stop' +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# ---- Validate inputs ------------------------------------------------------- +if (-not (Test-Path $CsvPath)) { throw "CsvPath not found: $CsvPath" } +if (-not (Test-Path $TemplatePath)) { throw "TemplatePath not found: $TemplatePath" } +if (-not (Test-Path $ToolPath)) { throw "ToolPath not found: $ToolPath" } + +$templates = @{ + 'Source\install.ps1' = Join-Path $TemplatePath "Source\install.ps1" + 'Source\uninstall.ps1' = Join-Path $TemplatePath "Source\uninstall.ps1" + 'Detection\detect.ps1' = Join-Path $TemplatePath "Detection\detect.ps1" +} +foreach ($k in $templates.Keys) { + if (-not (Test-Path $templates[$k])) { throw "Template missing: $($templates[$k])" } +} + +# ---- Load and filter CSV --------------------------------------------------- +$rows = Import-Csv -Path $CsvPath +$rows = $rows | Where-Object { $_.$FilterColumn -eq $FilterValue } +if (-not $rows) { + Write-Warning "No rows in $CsvPath match $FilterColumn=$FilterValue" + return +} + +Write-Host "Building $($rows.Count) package(s) to $OutputRoot" +Write-Host "" + +# ---- Per-location build loop ---------------------------------------------- +$results = foreach ($row in $rows) { + $slug = $row.Slug + $location = $row.LocationName + $token = $row.Token + $url = $row.UrlTemplate -replace '\{TOKEN\}', $token -replace '\{SLUG\}', $slug + + $pkgRoot = Join-Path $OutputRoot "$PackagePrefix-$slug" + $srcDir = Join-Path $pkgRoot "Source" + $detDir = Join-Path $pkgRoot "Detection" + $outDir = Join-Path $pkgRoot "Output" + + Write-Host "=== $location ($slug) ===" + + try { + # Scaffold + foreach ($d in @($pkgRoot, $srcDir, $detDir, $outDir)) { + if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d | Out-Null } + } + + # Copy templates + Copy-Item $templates['Source\install.ps1'] (Join-Path $srcDir 'install.ps1') -Force + Copy-Item $templates['Source\uninstall.ps1'] (Join-Path $srcDir 'uninstall.ps1') -Force + Copy-Item $templates['Detection\detect.ps1'] (Join-Path $detDir 'detect.ps1') -Force + + # Download MSI (preserve filename from URL) + $msiName = [System.IO.Path]::GetFileName([Uri]::new($url).AbsolutePath) + $msiPath = Join-Path $srcDir $msiName + + if ((Test-Path $msiPath) -and -not $Force) { + Write-Host " MSI cached: $msiName" + } else { + Write-Host " Downloading: $msiName" + if ($UseCurl) { + & curl.exe -sSL -o $msiPath $url + if ($LASTEXITCODE -ne 0) { throw "curl exit $LASTEXITCODE" } + } else { + Invoke-WebRequest -Uri $url -OutFile $msiPath -UseBasicParsing + } + } + + # Build .intunewin + Write-Host " Packaging..." + & $ToolPath -c $srcDir -s 'install.ps1' -o $outDir -q | Out-Null + if ($LASTEXITCODE -ne 0) { throw "IntuneWinAppUtil exit $LASTEXITCODE" } + + # Rename output for clarity + $built = Get-ChildItem -Path $outDir -Filter 'install.intunewin' -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $built) { throw "No .intunewin produced in $outDir" } + + $finalName = "$PackagePrefix-$slug.intunewin" + $finalPath = Join-Path $outDir $finalName + if (Test-Path $finalPath) { Remove-Item $finalPath -Force } + Rename-Item $built.FullName -NewName $finalName + Write-Host " -> $finalPath" + + [pscustomobject]@{ + Location = $location + Slug = $slug + Status = 'OK' + Output = $finalPath + } + } catch { + Write-Warning " FAILED: $_" + [pscustomobject]@{ + Location = $location + Slug = $slug + Status = "FAIL: $_" + Output = $null + } + } + Write-Host "" +} + +# ---- Summary --------------------------------------------------------------- +Write-Host "=== Build Summary ===" +$results | Format-Table Location, Slug, Status -AutoSize +$ok = ($results | Where-Object Status -eq 'OK').Count +Write-Host "$ok / $($results.Count) succeeded"