The need for ensuring device compliance in today’s IT landscape is paramount for operational stability and efficiency. We’ve effectively tackled this by leveraging Azure Enterprise Applications and PowerShell, significantly improving our PC rollouts and ensuring device compliance in Microsoft Intune.
The Challenge
During PC rollouts, our team encountered delays in devices achieving compliance status in Microsoft Intune, which led to deployment inconsistencies and impacted productivity. Users often faced access issues to critical resources.
The Solution: Azure Enterprise Application and PowerShell
Addressing these challenges, we developed a solution that not only streamlined the compliance check process but also introduced a level of automation and reliability.
Step 1: Creating an Azure Enterprise Application
1.2 Client Secret Generation
- Azure Portal: Go to Azure Active Directory > App registrations.
- New Registration: Enter the necessary details and set access settings.
1.1 Registering the Application
- Certificates & Secrets: Create and securely store a new client secret.
1.3 Assigning Permissions
- API Permissions: Add
DeviceManagementManagedDevices.Read.All
from Microsoft Graph.
1.4 Retrieving Essential IDs
- Overview Section: Make a note of the Tenant and Client IDs.
Step 2: Crafting the PowerShell Script
<#
.SYNOPSIS
Checks and logs the compliance status of a device using Azure Active Directory (Azure AD).
.DESCRIPTION
This script checks the compliance status of a device registered in Azure AD. It uses delegated permissions to read the device status. The script logs the device's compliance status and can handle both compliant and non-compliant states. It supports automatic retry logic with configurable intervals and timeout settings.
.PARAMETERS
TenantID - Azure AD tenant ID.
ClientID - Azure application client ID.
ClientSecret - Azure application client secret.
LogFolder - Directory where log files are stored.
CompliantTagFile - Path to a file that stores a tag when the device is compliant.
NonCompliantTagFile - Path to a file that stores a tag when the device is non-compliant.
LogFileName - Name of the log file.
CopyIntuneLog - Switch to enable copying the log to Intune log folder.
IntervalSeconds - Interval between compliance checks.
ComplianceTimeoutTries - Number of tries before timing out on compliance check.
NonComplianceTimeoutTries - Number of tries before timing out on non-compliance check.
Compliance - Switch to check for compliance.
NonCompliance - Switch to check for non-compliance.
EnableVerboseLog - Switch to enable verbose logging.
.EXAMPLE
.\Compliance-Check.ps1 -TenantID "example-tenant-id" -ClientID "example-client-id" -ClientSecret "example-client-secret" -Compliance
Checks if the device is compliant with the specified Azure AD credentials.
.EXAMPLE
.\Compliance-Check.ps1 -NonCompliance -CopyIntuneLog
Checks for device non-compliance and copies the log file to the Intune log folder.
.LINK
https://docs.microsoft.com/en-us/azure/active-directory/
.INPUTS
None. You cannot pipe objects to this script.
.OUTPUTS
String. The script outputs logs of the device compliance status.
.NOTES
Author: Hampus Klingenberg
Version: 1.1
Required Permissions: Read device status in Azure AD
#>
param (
[ValidateNotNullOrEmpty()]
[string]$TenantID = 'YOURTENANTID',
[ValidateNotNullOrEmpty()]
[string]$ClientID = 'YOURENTERPRISEAPPCLIENTID',
[ValidateNotNullOrEmpty()]
[string]$ClientSecret = (Get-Content "$PSScriptRoot\Secret.txt"),
[string]$LogFolder = "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs",
[string]$TagFolder = "C:\ProgramData\Tagfolder",
[string]$CompliantTagFile = "$TagFolder\Compliant.tag",
[string]$NonCompliantTagFile = "$TagFolder\Non-Compliant.tag",
[string]$LogFileName = $MyInvocation.MyCommand.Name.Replace(".ps1", ".log"),
[int]$IntervalSeconds = 5,
[int]$ComplianceTimeoutTries = 20,
[int]$NonComplianceTimeoutTries = 10,
[switch]$Pause,
[switch]$Transcript,
[switch]$CopyIntuneLog,
[switch]$Compliance,
[switch]$NonCompliance,
[switch]$EnableVerboseLog
)
function Write-Log {
<#
.SYNOPSIS
Logs messages with a timestamp and color to the console and appends them to a specified log file.
.DESCRIPTION
This function logs messages to the console with a timestamp and color. It also appends the messages to a log file specified by the -LogPath parameter. If the log file doesn't exist, it will be created. If -LogPath is not provided, the default log path specified by $LogFilePath will be used.
.PARAMETER Message
The message to be logged.
.PARAMETER Color
The color in which the message should be displayed in the console. Default is "White".
.PARAMETER LogPath
The path to the log file where messages will be appended. If the file doesn't exist, it will be created.
.EXAMPLE
Write-Log -Message "This is a log message" -LogPath "C:\Logs\MyLog.log"
#>
param(
[string]$Message,
[string]$Color = "White",
[string]$LogPath = $LogFilePath
)
if ($EnableVerboseLog) {
Write-Host (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") $Message -ForegroundColor $Color
}
Add-Content -Path $LogPath -Value ("[" + (Get-Date -Format "yyyy-MM-dd HH:mm:ss") + "] " + $Message)
}
function Check-DeviceCompliance {
<#
.SYNOPSIS
Checks the compliance status of a device in Azure AD for a specified number of attempts.
.DESCRIPTION
This function retrieves the compliance status of a device registered in Azure AD.
It uses 'dsregcmd /status' to obtain the device ID, then queries Azure AD for its compliance status.
The function continues checking the compliance status at 5-second intervals for the specified number of attempts.
.PARAMETER TenantID
The Tenant ID of your Azure AD.
.PARAMETER ClientID
The Client ID of your Azure application.
.PARAMETER ClientSecret
The Client Secret of your Azure application.
.EXAMPLE
Check-DeviceCompliance -TenantID "your-tenant-id" -ClientID "your-client-id" -ClientSecret "your-client-secret"
#>
param (
[Parameter(Mandatory=$true)]
[string]$TenantID,
[Parameter(Mandatory=$true)]
[string]$ClientID,
[Parameter(Mandatory=$true)]
[string]$ClientSecret
)
try {
# Prepare the body for obtaining the OAuth 2.0 token
$body = @{
client_id = $ClientID
scope = "https://graph.microsoft.com/.default"
client_secret = $ClientSecret
grant_type = "client_credentials"
}
# Obtain the OAuth 2.0 token for Azure AD
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Method Post -Body $body -ErrorAction Stop
$token = $tokenResponse.access_token
# Set headers for the Graph API call
$headers = @{
Authorization = "Bearer $token"
ContentType = "application/json"
}
# Retrieve the Azure AD device ID
$uri = "https://graph.microsoft.com/v1.0/devices`?`$filter=deviceId eq '$DeviceId'"
$azureID = ((Invoke-RestMethod -Uri $uri -Headers $headers -ErrorAction Stop).Value | Where { $_.deviceId -eq $DeviceID }).id
# Check the device's compliance status in Azure AD
$complianceStatus = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/devices/$azureID`?`$select=isCompliant" -Headers $headers -ErrorAction Stop).isCompliant
Write-Log "Azure AD returned device ID: $azureID and compliance status: $ComplianceStatus"
return $complianceStatus -eq 'True'
} catch {
Write-Log "Error checking device compliance: $_" -Color "Red"
return $False
}
}
# Main Script Execution
try {
# Ensure the log folder exists
if (!(Test-Path $LogFolder)) {
New-Item -Path $LogFolder -ItemType Directory -Force
}
# Ensure the log tag folder exists
if (!(Test-Path $TagFolder)) {
New-Item -Path $TagFolder -ItemType Directory -Force
}
# Initialize log file path
$LogFilePath = Join-Path $LogFolder $LogFileName
# Start Transcript if specified
if ($Transcript) {
$TranscriptFilePath = $LogFilePath.Replace(".log", "-Transcript.log")
Start-Transcript -Path $TranscriptFilePath -Force
}
# Clear existing log file if it exists
if (Test-Path $LogFilePath) {
Remove-Item -Path $LogFilePath -Force
}
# Check if the device is joined to Azure AD and obtain its ID. Also checks if powershell is runing as a 32-bit process. If so run dsreg in x64 Powershell.
$DsregCmdStatus = if([System.Environment]::Is64BitProcess){
dsregcmd.exe /status
} else {
$ps64 = Join-Path $env:SystemRoot "\sysnative\WindowsPowerShell\v1.0\powershell.exe"
& $ps64 -Command {dsregcmd.exe /status}
}
if ($DsregCmdStatus -match "DeviceId") {
$DeviceId = $DsregCmdStatus -match "DeviceID"
$DeviceId = ($DeviceId.Split(":").trim())
$DeviceId = $DeviceId[1]
Write-Log "Current device is joined to AAD and device ID is $DeviceId"
}
else {
Write-Log "Machine is not joined to AAD. Script will not run."
exit 1
}
# Compliance Check Logic
if ($Compliance -or $NonCompliance) {
$tries = 0
$isCompliant = $null
# Check the device's compliance status
do {
$tries++
$isCompliant = Check-DeviceCompliance -TenantID $TenantID -ClientID $ClientID -ClientSecret $ClientSecret
if (($Compliance -and $isCompliant) -or ($NonCompliance -and -not $isCompliant)) {
$status = if ($Compliance) { "compliant" } else { "non-compliant" }
Write-Log "Device is $status after $tries tries."
$tagFile = if ($Compliance) { $CompliantTagFile } else { $NonCompliantTagFile }
Add-Content -Path $tagFile -Value "Device is $status as of $(Get-Date)"
break
} else {
Write-Log "Device compliance status is not as expected. Retrying in $IntervalSeconds seconds."
Start-Sleep -Seconds $IntervalSeconds
}
} while ($tries -lt $(if ($Compliance) { $ComplianceTimeoutTries } else { $NonComplianceTimeoutTries }))
}
# Copy the log file to Intune log folder if specified
if ($CopyIntuneLog) {
$IntuneLogFolder = "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs"
if (!(Test-Path $IntuneLogFolder)) {
New-Item -Path $IntuneLogFolder -ItemType Directory -Force
}
Copy-Item -Path $LogFilePath -Destination $IntuneLogFolder -Force
}
# Stop the transcript if it was started
if ($Transcript) {
Stop-Transcript
}
# Pause the script if specified
if ($Pause) {
Read-Host "Press Enter to continue..."
}
} catch {
Write-Log "An error occurred: $_" -Color "Red"
exit 1
}
exit 0
How to Use the Script:
- Deploy as an Intune Win32 App: Include tenant ID, client ID, and client secret in the command line arguments.
- Security: Ensure credentials are handled securely.
- Customization: Add post-compliance actions as needed.
Step 3: Preparing and Adding the Script as a Required App in Intune
Prepare the Script
- Packaging the Script: Convert it into a deployable format using the IntuneWinAppUtil tool.
- Download and Use: Get it from its GitHub repository.
Upload and Configure in Intune
- In the Microsoft Endpoint Manager Admin Center: Go to
Apps
>All apps
>Add
, then selectWindows app (Win32)
.
- App Information: Upload the .intunewin file and complete the App Information section.
- Program: Enter the install command used by the app. This is the script with parameters. Full example in the section below.
powershell.exe -ExecutionPolicy Bypass -File "Path\To\YourScript.ps1" -tenantId "your-tenant-id" -clientId "your-client-id" -clientSecret "your-client-secret" -Compliance
- Detection Rule: Select the tag file you specified in the script as a detection rule.
Configure the Enrollment Status Page (ESP)
- Blocking Until Required Apps are Installed: Adjust ESP settings in
Devices
| Enrollment -> Enrollment Status Page -> NAMEOFYOURESP.
The Impact
Enhanced Stability in PC Rollouts
Our automated approach revamped the PC deployment process, ensuring compliance standards were met.
Reliable User Experience
Users benefited from improved access and setup consistency, enhancing satisfaction.
Efficient IT Operations
This solution reduced manual monitoring, allowing the IT team to focus on strategic tasks.
Conclusion
Implementing this solution in Microsoft Intune using Azure and PowerShell scripting was crucial for our IT management. It emphasized the value of automation in device compliance, leading to more stable, efficient, and secure IT environments.
We share this guide with the hope that it assists other IT teams in enhancing their device management strategies through cloud services and scripting.