Klingtech Logo
  • Home
  • Blog
  • ContactContact us

Automating Device Compliance with Azure Enterprise Applications and PowerShell

August 13, 2024

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 select Windows 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.

Next Post KlingShell: IoT Firmware Solution with Wi-Fi Connectivity and OTA Updates

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Post

  • KlingShell: IoT Firmware Solution with Wi-Fi Connectivity and OTA UpdatesAugust 13, 2024
  • Automating Device Compliance with Azure Enterprise Applications and PowerShellAugust 13, 2024

Klingtech

Norway

  • Facebook
  • LinkedIn
  • Google
  • Dribbble
  • Twitter
Contact Info

Email: info@klingtech.com

Copyright 2024 Klingtech AS. All Rights Reserved.

Privacy Policy

Terms & Conditions