Have you ever needed to deploy a Win32 app via Microsoft Intune to a group of users but wanted the app to install only on their new devices? I recently faced this challenge and found a unique solution. Unlike previous situations where detecting the Autopilot Enrollment Status Page (ESP) was enough, this time I had to account for a variety of scenarios: some users would use Autopilot, some wouldn’t, and others would use Windows 365 (Cloud PCs), which do not support Autopilot.
To address this, I devised a PowerShell script to use as a requirement rule for the Win32 app. This script determines if a computer is “new” by checking the Intune enrollment date from the registry and comparing it to the current date and time. If the enrollment date is within a specified number of hours, the script deems the computer as new.
Here’s the PowerShell script that accomplishes this:
<#
.SYNOPSIS
Finds the oldest Intune enrollment timestamp in the registry, compares it to the current date/time, and outputs true/false if the device was enrolled within the specified timeframe.
.DESCRIPTION
This script recursively searches the specified registry path for FirstScheduleTimestamp values,
finds the oldest timestamp, and compares it to the current date and time. It outputs a boolean
value indicating whether the oldest timestamp is within a defined number of hours from the current date and time.
.PARAMETER REGPath
The root registry path to search.
.PARAMETER Value
The registry value name to search for.
.PARAMETER CompareHours
The number of hours to compare the oldest timestamp against the current date and time.
.EXAMPLE
.\Check-FirstScheduleTimestamp.ps1 -REGPath 'HKLM:\SOFTWARE\Microsoft\Enrollments' -Value 'FirstScheduleTimestamp' -CompareHours 24
.NOTES
Script_Name: Check-FirstScheduleTimestamp.ps1
Author: John Marcum (PJM)
Date: 7/2/2024
Version: 1.0
This script can be used as a requirement rule for Intune Win32 app deployments to control installation timing based on maintenance windows.
# LEGAL DISCLAIMER
# This script is provided "as is" without any warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability, fitness for a particular purpose, or non-infringement. The entire risk as to the quality and performance of the script is with you.
# In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the script or the use or other dealings in the script.
# You should never run any script from the Internet without understanding its contents and effects. It is highly recommended that you thoroughly test the script in a safe environment before running it in production.
#>
param (
[string]$REGPath = 'HKLM:\SOFTWARE\Microsoft\Enrollments',
[string]$Value = 'FirstScheduleTimestamp',
[int]$CompareHours = 24
)
### BEGIN FUNCTIONS ###
function Get-KeyPath {
# Get-ChildItem skips top level key, use Get-Item for that
param([parameter(ValueFromPipeline)]$key)
process {
$key.GetValueNames() | ForEach-Object {
$value = $_
[pscustomobject] @{
Path = $key.PSPath
Name = $value
Value = $key.GetValue($value)
Type = $key.GetValueKind($value)
}
}
}
}
function Convert-BytesToDateTime {
param (
[byte[]]$bytes
)
function GetValue ($array){
return [uint32]('0x' + (($array | ForEach-Object { $_.ToString("X2") }) -join ''))
}
[array]::Reverse($bytes)
$dateTime = New-Object DateTime (
(GetValue $bytes[14..15]),
(GetValue $bytes[12..13]),
(GetValue $bytes[8..9]),
(GetValue $bytes[6..7]),
(GetValue $bytes[4..5]),
(GetValue $bytes[2..3]),
(GetValue $bytes[0..1])
)
return $dateTime
}
### END FUNCTIONS ###
### SCRIPT ENTRY POINT ###
# Find all paths with the specified registry value
$TimestampPaths = Get-ChildItem -Recurse $REGPath | Get-KeyPath | Where-Object Name -eq $Value | Select-Object -ExpandProperty Path -ErrorAction SilentlyContinue
# If one or more Intune enrollment times were found, get the oldest
if ($TimestampPaths) {
$oldestTimestamp = $null
$oldestPath = $null
foreach ($Path in $TimestampPaths) {
Write-Host "Searching $($Path)"
$Data = Get-ItemProperty -Path $Path -Name $Value | Select-Object -ExpandProperty $Value -ErrorAction SilentlyContinue
if ($Data) {
# Convert the binary data to DateTime
$scheduleDateTime = Convert-BytesToDateTime -bytes $Data
# Determine if this is the oldest timestamp found
if (-not $oldestTimestamp -or $scheduleDateTime -lt $oldestTimestamp) {
$oldestTimestamp = $scheduleDateTime
$oldestPath = $Path
}
}
}
if ($oldestTimestamp) {
Write-Host "Oldest FirstScheduleTimestamp found in $($oldestPath): $($oldestTimestamp)"
# Get the current date and time
$currentDateTime = Get-Date
# Compare the oldest DateTime to the current date and time
$timeDifference = $currentDateTime - $oldestTimestamp
if ($timeDifference.TotalHours -le $CompareHours) {
# Device was enrolled within the specified timeframe
Write-Output $true
} else {
# Device was not enrolled within the specified timeframe
Write-Output $false
}
} else {
# No oldest timestamp found. We should never land here.
Write-Output $false
}
} else {
# No enrollment timestamps found. The device is likely not enrolled in Intune.
Write-Output $false
}
To use this script as a requirement rule in an Intune Win32 app deployment, follow these steps:
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |