diff --git a/AD/Add-ADFSDeviceClaimRules.ps1 b/AD/Add-ADFSDeviceClaimRules.ps1 new file mode 100644 index 0000000..c3ebd16 --- /dev/null +++ b/AD/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(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)$"] + => 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 "" diff --git a/AD/Apply-NameTemplate.ps1 b/AD/Apply-NameTemplate.ps1 new file mode 100644 index 0000000..3e29a4b --- /dev/null +++ b/AD/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/AD/Check-ADHealth.ps1 b/AD/Check-ADHealth.ps1 new file mode 100644 index 0000000..c1dc063 --- /dev/null +++ b/AD/Check-ADHealth.ps1 @@ -0,0 +1,113 @@ +# 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 +} + +# 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 +} + +# Function to handle errors +function Handle-Error { + param($error) + Write-Log "ERROR: $error" +} + +# 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" +} +catch { + Handle-Error $_ + Write-Log "Script terminated with errors" +} +finally { + Stop-Transcript +} diff --git a/AD/Check-ADHealth_NEW.ps1 b/AD/Check-ADHealth_NEW.ps1 new file mode 100644 index 0000000..c829ae5 --- /dev/null +++ b/AD/Check-ADHealth_NEW.ps1 @@ -0,0 +1,149 @@ +$comp = $env:computername +$org = "YourCompanyName" # Update with your organization's name +$baseDir = "D:\Temp" # Base directory for script execution +$orgDir = Join-Path -Path $baseDir -ChildPath $org + +# 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 +} + +# Function to handle errors +function Handle-Error { + param($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 + 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 +$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 +} + +# 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 information + $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 information + $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." diff --git a/AD/Check-ADHealth_Updated.ps1 b/AD/Check-ADHealth_Updated.ps1 new file mode 100644 index 0000000..23ebeb9 --- /dev/null +++ b/AD/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." diff --git a/AD/Check-NameTemplate.ps1 b/AD/Check-NameTemplate.ps1 new file mode 100644 index 0000000..9265021 --- /dev/null +++ b/AD/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 +} diff --git a/AD/Configure-NDESPrereqs.ps1 b/AD/Configure-NDESPrereqs.ps1 new file mode 100644 index 0000000..5d6dccb --- /dev/null +++ b/AD/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." diff --git a/AD/Delete-GroupPolicyAnalyticsReports.ps1 b/AD/Delete-GroupPolicyAnalyticsReports.ps1 new file mode 100644 index 0000000..3968726 --- /dev/null +++ b/AD/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) - $_" + } +} diff --git a/Get-UninstallStrings.ps1 b/AD/Get-ComputerTimestamps.ps1 similarity index 97% rename from Get-UninstallStrings.ps1 rename to AD/Get-ComputerTimestamps.ps1 index 1cec4e2..7c957f4 100644 --- a/Get-UninstallStrings.ps1 +++ b/AD/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 diff --git a/AD/Get-GPOReports.ps1 b/AD/Get-GPOReports.ps1 new file mode 100644 index 0000000..55434bc --- /dev/null +++ b/AD/Get-GPOReports.ps1 @@ -0,0 +1,68 @@ +<# +.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" +#> + +param( +[string]$Domain = $env:USERDNSDOMAIN, +[string]$OutputPath = "C:\GPOReports", +[ValidateSet("XML", "HTML", "Both")] +[string]$ReportType = "Both" +) + +# Create domain-specific subfolder +$domainFolder = Join-Path $OutputPath ($Domain -replace '\.', '_') +if (!(Test-Path $domainFolder)) { +New-Item -ItemType Directory -Path $domainFolder -Force | Out-Null +} + +# 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 '[\\/:*?"<>|]', '_' + +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 +} + +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 + diff --git a/AD/GroupPolicyAnalyticsCleanup.js b/AD/GroupPolicyAnalyticsCleanup.js new file mode 100644 index 0000000..26f0915 --- /dev/null +++ b/AD/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(); diff --git a/AD/Sync-OUToSecurityGroups-Scheduled.ps1 b/AD/Sync-OUToSecurityGroups-Scheduled.ps1 new file mode 100644 index 0000000..56295ec --- /dev/null +++ b/AD/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 +} diff --git a/AD/Validate-NDESConfiguration.ps1 b/AD/Validate-NDESConfiguration.ps1 new file mode 100644 index 0000000..73ac914 --- /dev/null +++ b/AD/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 + +} diff --git a/BitLocker/Escrow-BitLocker_to_AAD.ps1 b/BitLocker/Escrow-BitLocker_to_AAD.ps1 new file mode 100644 index 0000000..91d8ecf --- /dev/null +++ b/BitLocker/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." +} diff --git a/BitLocker/Find-MissingKeyInBitlocker.ps1 b/BitLocker/Find-MissingKeyInBitlocker.ps1 new file mode 100644 index 0000000..60852b7 --- /dev/null +++ b/BitLocker/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 diff --git a/BitLocker/Get-BitLockerInfo.ps1 b/BitLocker/Get-BitLockerInfo.ps1 new file mode 100644 index 0000000..b79367f --- /dev/null +++ b/BitLocker/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 diff --git a/BitLocker/Initiate-BitLocker.ps1 b/BitLocker/Initiate-BitLocker.ps1 new file mode 100644 index 0000000..fbf1ff7 --- /dev/null +++ b/BitLocker/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 +} diff --git a/Check-ADHealth.ps1 b/Check-ADHealth.ps1 deleted file mode 100644 index 05c5ffc..0000000 --- a/Check-ADHealth.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -$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 -} - -# 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 -} - - -# 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 -} - -# 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 -} - -# 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 -} - - -Stop-Transcript 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/Get-GPOReports.ps1 b/Get-GPOReports.ps1 deleted file mode 100644 index 8d11412..0000000 --- a/Get-GPOReports.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -# Get all GPOs and generate XML report for each - -# Set the output path where the XML reports will be saved - -$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 - -# Loop through each GPO and export it to an XML report - -foreach ($gpo in $allGPOs) { - -$gpoReport = get-gporeport $gpo.DisplayName -ReportType xml - -out-File -filepath ($outputpath + $gpo.DisplayName + ".xml") -inputobject $gpoReport - -Write-Host "Exported GPO $($gpo.DisplayName)" - -} - -} \ No newline at end of file diff --git a/Intune/Build-IntuneAppBuilder.ps1 b/Intune/Build-IntuneAppBuilder.ps1 new file mode 100644 index 0000000..dcbe4f1 --- /dev/null +++ b/Intune/Build-IntuneAppBuilder.ps1 @@ -0,0 +1,238 @@ +<# +.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. + +.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 + 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( + [string]$AppName = "App", + [string]$BasePath = (Join-Path -Path $PSScriptRoot -ChildPath $AppName), + [string]$SetupFile = "install.ps1", + [string]$ToolPath, + [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 resolution --------------------------------------- +$toolUrl = "https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/master/IntuneWinAppUtil.exe" + +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..." + 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" +} 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" diff --git a/Intune/Clean-IntuneEnrollment.ps1 b/Intune/Clean-IntuneEnrollment.ps1 new file mode 100644 index 0000000..ea0196f --- /dev/null +++ b/Intune/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 diff --git a/Intune/Create-StaleDeviceGroup.ps1 b/Intune/Create-StaleDeviceGroup.ps1 new file mode 100644 index 0000000..29f19a6 --- /dev/null +++ b/Intune/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'" diff --git a/Intune/Delete-StaleDeviceByGroup.ps1 b/Intune/Delete-StaleDeviceByGroup.ps1 new file mode 100644 index 0000000..222d485 --- /dev/null +++ b/Intune/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" +} diff --git a/Intune/Enhanced-AppX-Cleanup.ps1 b/Intune/Enhanced-AppX-Cleanup.ps1 new file mode 100644 index 0000000..9c2d082 --- /dev/null +++ b/Intune/Enhanced-AppX-Cleanup.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 diff --git a/Intune/Enroll-Intune.ps1 b/Intune/Enroll-Intune.ps1 new file mode 100644 index 0000000..7a40b97 --- /dev/null +++ b/Intune/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 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 +} diff --git a/Intune/Force-Sync.ps1 b/Intune/Force-Sync.ps1 new file mode 100644 index 0000000..24c1711 --- /dev/null +++ b/Intune/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 diff --git a/Get-DomainComputerTimestamps.ps1 b/Intune/Get-InstalledSoftware.ps1 similarity index 82% rename from Get-DomainComputerTimestamps.ps1 rename to Intune/Get-InstalledSoftware.ps1 index 6d3197a..2fbd320 100644 --- a/Get-DomainComputerTimestamps.ps1 +++ b/Intune/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 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/Intune-AutoEnrollment-HybridJoined.ps1 b/Intune/Intune-AutoEnrollment-HybridJoined.ps1 new file mode 100644 index 0000000..aacb40b --- /dev/null +++ b/Intune/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 diff --git a/Intune/Intune-AutoEnrollment-Usage.md b/Intune/Intune-AutoEnrollment-Usage.md new file mode 100644 index 0000000..0a5cc4c --- /dev/null +++ b/Intune/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/Intune-AutoEnrollment.ps1 b/Intune/Intune-AutoEnrollment.ps1 new file mode 100644 index 0000000..f1f895b --- /dev/null +++ b/Intune/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 diff --git a/Intune/Intune-AutomationRunbookv1_0.ps1 b/Intune/Intune-AutomationRunbookv1_0.ps1 new file mode 100644 index 0000000..40bcc1b --- /dev/null +++ b/Intune/Intune-AutomationRunbookv1_0.ps1 @@ -0,0 +1,3786 @@ +<# +.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 { + +<# +.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/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(){ + +<# +.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 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_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_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" + +$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 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 + + + + diff --git a/Intune/Intune-AutomationRunbookv2_1.ps1 b/Intune/Intune-AutomationRunbookv2_1.ps1 new file mode 100644 index 0000000..f0edbde --- /dev/null +++ b/Intune/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 + + + + 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`"" diff --git a/Intune/Nuclear-Appx-Cleanup.ps1 b/Intune/Nuclear-Appx-Cleanup.ps1 new file mode 100644 index 0000000..642d6b4 --- /dev/null +++ b/Intune/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 diff --git a/Intune/Set-AutomaticTimezone.ps1 b/Intune/Set-AutomaticTimezone.ps1 new file mode 100644 index 0000000..5eac556 --- /dev/null +++ b/Intune/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 diff --git a/Intune/Set-EnrollmentAttribute.ps1 b/Intune/Set-EnrollmentAttribute.ps1 new file mode 100644 index 0000000..9474b29 --- /dev/null +++ b/Intune/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." +} +} diff --git a/Intune/Set-EnrollmentAttribute_RANGE.ps1 b/Intune/Set-EnrollmentAttribute_RANGE.ps1 new file mode 100644 index 0000000..0316670 --- /dev/null +++ b/Intune/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)." + } +} diff --git a/Intune/Sysprep-PreCleanup.ps1 b/Intune/Sysprep-PreCleanup.ps1 new file mode 100644 index 0000000..865bcba --- /dev/null +++ b/Intune/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 diff --git a/Intune/intune_enrollment.bat b/Intune/intune_enrollment.bat new file mode 100644 index 0000000..fc69851 --- /dev/null +++ b/Intune/intune_enrollment.bat @@ -0,0 +1 @@ +c:\windows\system32\deviceenroller.exe /c /AutoEnrollMDM \ No newline at end of file diff --git a/ProactiveRemediation/Detection/Detect-CarbonBlack.ps1 b/ProactiveRemediation/Detection/Detect-CarbonBlack.ps1 new file mode 100644 index 0000000..3e7a3c2 --- /dev/null +++ b/ProactiveRemediation/Detection/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/ProactiveRemediation/Detection/Detect-DiskSpace.ps1 b/ProactiveRemediation/Detection/Detect-DiskSpace.ps1 new file mode 100644 index 0000000..834069d --- /dev/null +++ b/ProactiveRemediation/Detection/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/ProactiveRemediation/Detection/Detect-FixedDrive.ps1 b/ProactiveRemediation/Detection/Detect-FixedDrive.ps1 new file mode 100644 index 0000000..6d63849 --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-FixedDrive.ps1 @@ -0,0 +1,17 @@ +# Get the fixed drives +$FixedDrives = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" + +# 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 non-OS fixed drives found." + exit 0 # Zero exit code indicates no non-OS fixed drives +} diff --git a/ProactiveRemediation/Detection/Detect-IME.ps1 b/ProactiveRemediation/Detection/Detect-IME.ps1 new file mode 100644 index 0000000..94e0af1 --- /dev/null +++ b/ProactiveRemediation/Detection/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/ProactiveRemediation/Detection/Detect-IntuneCache.ps1 b/ProactiveRemediation/Detection/Detect-IntuneCache.ps1 new file mode 100644 index 0000000..c8bb9a4 --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-IntuneCache.ps1 @@ -0,0 +1,12 @@ +# 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 + +if ($folderSize -gt $maxSize) { + write-host "Remediation needed" -foregroundcolor red + exit 1 # Remediation needed +} else { + write-host "No remediation needed" -foregroundcolor green + exit 0 # No remediation needed +} diff --git a/ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 b/ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 new file mode 100644 index 0000000..a5cf07c --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-IntuneCoManageSettings.ps1 @@ -0,0 +1,117 @@ +# 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 - will remediate" + exit 1 + } + + # 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 + diff --git a/ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 b/ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 new file mode 100644 index 0000000..597ca38 --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-IntuneUpdateSettings.ps1 @@ -0,0 +1,84 @@ +<# + 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', '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' + ) + } +) + +# 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 diff --git a/ProactiveRemediation/Detection/Detect-MdmEnrollmentBlock.ps1 b/ProactiveRemediation/Detection/Detect-MdmEnrollmentBlock.ps1 new file mode 100644 index 0000000..f34f700 --- /dev/null +++ b/ProactiveRemediation/Detection/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 +} diff --git a/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 b/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 new file mode 100644 index 0000000..287a2cf --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-SCCMClientAgent.ps1 @@ -0,0 +1,98 @@ +<# +.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/Detection/Detect-TimeoutWarning.ps1 b/ProactiveRemediation/Detection/Detect-TimeoutWarning.ps1 new file mode 100644 index 0000000..9bbd56a --- /dev/null +++ b/ProactiveRemediation/Detection/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/ProactiveRemediation/Detection/Detect-WUEntries.ps1 b/ProactiveRemediation/Detection/Detect-WUEntries.ps1 new file mode 100644 index 0000000..4812b38 --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-WUEntries.ps1 @@ -0,0 +1,79 @@ +# Detection Script for Windows Update Policy Conflicts +# Checks for conflicting GPO/WSUS registry entries that prevent Intune Windows Update management + +try { + $ConflictFound = $false + $ConflictingEntries = @() + + # Define registry paths to check + $RegistryPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate", + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + ) + + # Define problematic registry values that indicate GPO/WSUS control + $ProblematicValues = @( + "WUServer", + "WUStatusServer", + "TargetGroup", + "TargetGroupEnabled", + "AcceptTrustedPublisherCerts", + "ElevateNonAdmins", + "AUOptions", + "NoAutoUpdate", + "DisableDualScan" + ) + + # Check each registry path + foreach ($Path in $RegistryPaths) { + if (Test-Path $Path) { + Write-Output "Checking registry path: $Path" + + # Get all properties in the registry key + $RegKey = Get-ItemProperty -Path $Path -ErrorAction SilentlyContinue + + 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)" + } + } + } + } + } + + # Additional check for AU (Automatic Updates) registry entries + $AUPath = "HKLM:\SOFTWARE\Microsoft\Windows\WindowsUpdate\AU" + if (Test-Path $AUPath) { + $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)" + } + } + } + } + + # Log results + 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 "DETECTION RESULT: No Windows Update policy conflicts found. Intune can manage updates properly." + exit 0 # Exit success - no remediation needed + } +} +catch { + Write-Error "Detection script failed: $($_.Exception.Message)" + exit 1 +} diff --git a/ProactiveRemediation/Detection/Detect-WUServer.ps1 b/ProactiveRemediation/Detection/Detect-WUServer.ps1 new file mode 100644 index 0000000..0c49630 --- /dev/null +++ b/ProactiveRemediation/Detection/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 +} diff --git a/ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 b/ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 new file mode 100644 index 0000000..b16c97b --- /dev/null +++ b/ProactiveRemediation/Detection/Detect-WingetUpdates.ps1 @@ -0,0 +1,14 @@ +# 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 { + # No upgrades available, all apps are up-to-date + Write-Host "All applications are up-to-date" + exit 0 # Exit with 0 indicating compliance +} diff --git a/ProactiveRemediation/Detection/Detect-WiredAutoConfig.ps1 b/ProactiveRemediation/Detection/Detect-WiredAutoConfig.ps1 new file mode 100644 index 0000000..2b690d4 --- /dev/null +++ b/ProactiveRemediation/Detection/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 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/ProactiveRemediation/Remediation/Remediate-AU.ps1 b/ProactiveRemediation/Remediation/Remediate-AU.ps1 new file mode 100644 index 0000000..e93bf75 --- /dev/null +++ b/ProactiveRemediation/Remediation/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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-CarbonBlack.ps1 b/ProactiveRemediation/Remediation/Remediate-CarbonBlack.ps1 new file mode 100644 index 0000000..40330be --- /dev/null +++ b/ProactiveRemediation/Remediation/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 /uninstall $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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-DisableDualScan.ps1 b/ProactiveRemediation/Remediation/Remediate-DisableDualScan.ps1 new file mode 100644 index 0000000..78c843b --- /dev/null +++ b/ProactiveRemediation/Remediation/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 diff --git a/ProactiveRemediation/Remediation/Remediate-DiskSpace.ps1 b/ProactiveRemediation/Remediation/Remediate-DiskSpace.ps1 new file mode 100644 index 0000000..aae958e --- /dev/null +++ b/ProactiveRemediation/Remediation/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" diff --git a/ProactiveRemediation/Remediation/Remediate-IME.ps1 b/ProactiveRemediation/Remediation/Remediate-IME.ps1 new file mode 100644 index 0000000..749e18e --- /dev/null +++ b/ProactiveRemediation/Remediation/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" +} diff --git a/ProactiveRemediation/Remediation/Remediate-IntuneCache.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneCache.ps1 new file mode 100644 index 0000000..9e75e60 --- /dev/null +++ b/ProactiveRemediation/Remediation/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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 new file mode 100644 index 0000000..ab1d5d6 --- /dev/null +++ b/ProactiveRemediation/Remediation/Remediate-IntuneCoManageSettings.ps1 @@ -0,0 +1,190 @@ +# 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 showing as assigned to Intune. Will continue with remediation steps." + # exit 0 + } + + Write-Host "Starting remediation for device" + + # 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 = @( + "SetPolicyDrivenUpdateSourceForFeatureUpdates", + "SetPolicyDrivenUpdateSourceForQualityUpdates", + "SetPolicyDrivenUpdateSourceForDriverUpdates", + "SetPolicyDrivenUpdateSourceForOtherUpdates" + ) + + 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 { + $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 + 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 + $searchResult = $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 + + + diff --git a/ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 b/ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 new file mode 100644 index 0000000..668ed4e --- /dev/null +++ b/ProactiveRemediation/Remediation/Remediate-IntuneUpdateSettings.ps1 @@ -0,0 +1,195 @@ +<# + 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 - 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 values and keys +function Remove-WSUSRegistry { + param ( + [string]$RegistryPath + ) + + if (-not (Test-Path $RegistryPath)) { + Write-Output "Path not present: $RegistryPath" + return $true + } + + try { + # Get all properties first for logging + $properties = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue + $wsusProperties = @() + + # 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' + ) + + # Check which WSUS properties exist + foreach ($valueName in $wsusValueNames) { + if ($null -ne $properties.$valueName) { + $wsusProperties += "$valueName = $($properties.$valueName)" + } + } + + 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" + } + + return $true + } + catch { + Write-Output "ERROR: Failed to process $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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-MdmEnrollmentBlock.ps1 b/ProactiveRemediation/Remediation/Remediate-MdmEnrollmentBlock.ps1 new file mode 100644 index 0000000..b9955dc --- /dev/null +++ b/ProactiveRemediation/Remediation/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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-NoAutoUpdate.ps1 b/ProactiveRemediation/Remediation/Remediate-NoAutoUpdate.ps1 new file mode 100644 index 0000000..ce6b903 --- /dev/null +++ b/ProactiveRemediation/Remediation/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 diff --git a/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 b/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 new file mode 100644 index 0000000..9490809 --- /dev/null +++ b/ProactiveRemediation/Remediation/Remediate-SCCMClientAgent.ps1 @@ -0,0 +1,171 @@ +<# +.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 diff --git a/ProactiveRemediation/Remediation/Remediate-TimeoutWarning.ps1 b/ProactiveRemediation/Remediation/Remediate-TimeoutWarning.ps1 new file mode 100644 index 0000000..4758b59 --- /dev/null +++ b/ProactiveRemediation/Remediation/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") + } +} diff --git a/ProactiveRemediation/Remediation/Remediate-WSUS_LastOnlineScan.ps1 b/ProactiveRemediation/Remediation/Remediate-WSUS_LastOnlineScan.ps1 new file mode 100644 index 0000000..bcc86a4 --- /dev/null +++ b/ProactiveRemediation/Remediation/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: $_" +} diff --git a/ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 b/ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 new file mode 100644 index 0000000..943588e --- /dev/null +++ b/ProactiveRemediation/Remediation/Remediate-WUEntries.ps1 @@ -0,0 +1,213 @@ +# 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", + "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + ) + + # Define problematic registry values to remove + $ProblematicValues = @( + "WUServer", + "WUStatusServer", + "TargetGroup", + "TargetGroupEnabled", + "AcceptTrustedPublisherCerts", + "ElevateNonAdmins", + "AUOptions", + "NoAutoUpdate", + "DisableDualScan" + ) + + # Quick function to check if we're approaching timeout + function Test-TimeoutApproaching { + $ElapsedMinutes = ((Get-Date) - $ScriptStartTime).TotalMinutes + return $ElapsedMinutes -gt $MaxExecutionMinutes + } + + # 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 - 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" + + # 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 access $Path : $($_.Exception.Message)" + } + } + } + + # 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 { + $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 access AU path: $($_.Exception.Message)" + } + } + } + + # 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 { + $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)" + } + } + } + } + + # OPTIMIZED: Skip gpupdate and complex service restarts if approaching timeout + if ($RemediationApplied) { + 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 { + # 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." + Write-Output "Intune can now manage updates. A device restart is recommended." + exit 0 + } + else { + Write-Output "REMEDIATION RESULT: No conflicting entries found to remove." + exit 0 + } +} +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 + 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 +} diff --git a/ProactiveRemediation/Remediation/Remediate-WUServer.ps1 b/ProactiveRemediation/Remediation/Remediate-WUServer.ps1 new file mode 100644 index 0000000..1ca7538 --- /dev/null +++ b/ProactiveRemediation/Remediation/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 diff --git a/ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 b/ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 new file mode 100644 index 0000000..fac146e --- /dev/null +++ b/ProactiveRemediation/Remediation/Remediate-WingetUpdates.ps1 @@ -0,0 +1,9 @@ +# 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." +} diff --git a/ProactiveRemediation/Remediation/Remediate-WiredAutoConfig.ps1 b/ProactiveRemediation/Remediation/Remediate-WiredAutoConfig.ps1 new file mode 100644 index 0000000..e9ce060 --- /dev/null +++ b/ProactiveRemediation/Remediation/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 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 +} diff --git a/Utilities/Backup-Permissions.ps1 b/Utilities/Backup-Permissions.ps1 new file mode 100644 index 0000000..b962a9b --- /dev/null +++ b/Utilities/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 +} diff --git a/Utilities/Change-Ownership.ps1 b/Utilities/Change-Ownership.ps1 new file mode 100644 index 0000000..a8cda57 --- /dev/null +++ b/Utilities/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 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/Utilities/Discover-Printers_CIM_WMI.ps1 b/Utilities/Discover-Printers_CIM_WMI.ps1 new file mode 100644 index 0000000..6766597 --- /dev/null +++ b/Utilities/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" diff --git a/Utilities/Discover-Printers_PS.ps1 b/Utilities/Discover-Printers_PS.ps1 new file mode 100644 index 0000000..c050c8a --- /dev/null +++ b/Utilities/Discover-Printers_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: $_" + } +} diff --git a/Utilities/Exchange-MailboxRetentionPolicyManager.ps1 b/Utilities/Exchange-MailboxRetentionPolicyManager.ps1 new file mode 100644 index 0000000..5ca543d --- /dev/null +++ b/Utilities/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" +} diff --git a/Utilities/Get-Counts.ps1 b/Utilities/Get-Counts.ps1 new file mode 100644 index 0000000..aaf9795 --- /dev/null +++ b/Utilities/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" 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/WindowsUpdate/Check-AU.ps1 b/WindowsUpdate/Check-AU.ps1 new file mode 100644 index 0000000..99e5509 --- /dev/null +++ b/WindowsUpdate/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/WindowsUpdate/Check-DisableDualScan.ps1 b/WindowsUpdate/Check-DisableDualScan.ps1 new file mode 100644 index 0000000..975a2fe --- /dev/null +++ b/WindowsUpdate/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/WindowsUpdate/Check-NoAutoUpdate.ps1 b/WindowsUpdate/Check-NoAutoUpdate.ps1 new file mode 100644 index 0000000..ba7249f --- /dev/null +++ b/WindowsUpdate/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/WindowsUpdate/Check-WSUS_LastOnlineScan.ps1 b/WindowsUpdate/Check-WSUS_LastOnlineScan.ps1 new file mode 100644 index 0000000..2c3fe09 --- /dev/null +++ b/WindowsUpdate/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/WindowsUpdate/WindowsUpdate-Reset.ps1 b/WindowsUpdate/WindowsUpdate-Reset.ps1 new file mode 100644 index 0000000..a8202be --- /dev/null +++ b/WindowsUpdate/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