Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/functions/private/ConvertTo-IntuneDeviceSummary.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function ConvertTo-IntuneDeviceSummary {
<#
.SYNOPSIS
Converts a managed device Graph object to the module's Intune device summary shape.

.DESCRIPTION
Maps selected managed device properties to a stable output object used by Get-IntuneDevice.
Expects any enrichment (for example EnrolledBy resolution) to be done before conversion.

.PARAMETER Device
A Microsoft Graph managed device object.

.EXAMPLE
$device | ConvertTo-IntuneDeviceSummary

Converts each input device object to the standardized output object.

.INPUTS
System.Object

.OUTPUTS
PSCustomObject
#>

[OutputType([PSCustomObject])]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[ValidateNotNull()]
[object]$Device
)

process {
$lastSyncDateTime = $null
# Keep output type stable even if Graph returns an unexpected timestamp format.
if ($null -ne $Device.lastSyncDateTime -and -not [string]::IsNullOrWhiteSpace([string]$Device.lastSyncDateTime)) {
try {
$lastSyncDateTime = [datetime]$Device.lastSyncDateTime
} catch {
$lastSyncDateTime = $null
}
}

# Stable output contract consumed by Get-IntuneDevice callers.
[PSCustomObject]@{
DeviceName = [string]$Device.deviceName
PrimaryUser = [string]$Device.userPrincipalName
DeviceManufacturer = [string]$Device.manufacturer
DeviceModel = [string]$Device.model
OperatingSystem = [string]$Device.operatingSystem
SerialNumber = [string]$Device.serialNumber
Compliance = [string]$Device.complianceState
LastSyncDateTime = $lastSyncDateTime
}
} # Process
} # Cmdlet
70 changes: 59 additions & 11 deletions src/functions/private/Resolve-IntuneDeviceByName.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,71 @@
}

process {
# deviceName is case-insensitive in OData. Exact match.
$encoded = [uri]::EscapeDataString("deviceName eq '$Name'")
$uri = "$baseUri`?`$filter=$encoded&`$select=id,deviceName"
# Escape only the string literal content; keep OData filter syntax intact.
$escapedName = $Name.Replace("'", "''")
$filter = "deviceName eq '$escapedName'"
$select = 'id,deviceName,userPrincipalName,manufacturer,model,operatingSystem,serialNumber,enrolledByUserId,complianceState,lastSyncDateTime'
$candidateUris = @(
"$baseUri`?`$filter=$filter&`$select=$select",
"$baseUri`?`$filter=$filter",
"$baseUri`?`$select=$select",
$baseUri
)

$resp = Invoke-GraphGet -Uri $uri
$resp = $null
$lastBadRequestError = $null

if ($null -eq $resp.value -or $resp.value.Count -eq 0) {
foreach ($candidateUri in $candidateUris) {
try {
$resp = Invoke-GraphGet -Uri $candidateUri
break
} catch {
$errorMessage = $_.Exception.Message
if ($errorMessage -match 'BadRequest|400') {
$lastBadRequestError = $_
Write-Verbose -Message "Managed device query returned BadRequest for URI '$candidateUri'. Trying next fallback."
continue
}

throw
}
}

if ($null -eq $resp -and $null -ne $lastBadRequestError) {
throw $lastBadRequestError
}

$devices = @()
if ($null -ne $resp) {
if ($null -ne $resp.value) {
$devices = @($resp.value)
} else {
$devices = @($resp)
}
}

# Keep exact name semantics even after fallback to unfiltered Graph query.
$matchedDevices = @($devices | Where-Object -FilterScript { [string]$_.deviceName -ieq $Name })

if ($matchedDevices.Count -eq 0) {
Write-Verbose -Message "No managed devices found with deviceName '$Name'."
return [PSCustomObject[]]@()
}

# Return PSCustomObject with Id and DeviceName
$resp.value | ForEach-Object -Process {
# Return managed device objects with fields required by downstream callers.
$matchedDevices | ForEach-Object -Process {
[PSCustomObject]@{
Id = $_.id
DeviceName = $_.deviceName
id = $_.id
deviceName = $_.deviceName
userPrincipalName = $_.userPrincipalName
manufacturer = $_.manufacturer
model = $_.model
operatingSystem = $_.operatingSystem
serialNumber = $_.serialNumber
enrolledByUserId = $_.enrolledByUserId
complianceState = $_.complianceState
lastSyncDateTime = $_.lastSyncDateTime
}
}
}
}
} # Process
} # Cmdlet
120 changes: 120 additions & 0 deletions src/functions/private/Resolve-IntuneDeviceByUser.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
function Resolve-IntuneDeviceByUser {
<#
.SYNOPSIS
Resolves one or more Intune managed devices by primary user UPN.

.DESCRIPTION
Queries Intune managed devices using the userPrincipalName filter.
Performs case-insensitive exact match searching via OData filter.
Returns all devices assigned to the specified user.

.PARAMETER UserPrincipalName
The UPN of the primary user to search for in Intune managed devices.

.EXAMPLE
Resolve-IntuneDeviceByUser -UserPrincipalName "jane.doe@contoso.com"

Returns all managed device objects whose primary user is jane.doe@contoso.com.

.INPUTS
System.String

.OUTPUTS
PSCustomObject[]

.NOTES
Part of the Intune Device helper functions.
Uses Microsoft Graph /beta endpoint.
Requires DeviceManagementManagedDevices.Read.All scope.
#>

[OutputType([PSCustomObject[]])]
[CmdletBinding()]
param(
[Parameter(
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = "The UPN of the primary user to resolve"
)]
[ValidateNotNullOrEmpty()]
[string]$UserPrincipalName
)

begin {
$baseUri = 'https://graph.microsoft.com/beta/deviceManagement/managedDevices'
$select = 'id,deviceName,userPrincipalName,manufacturer,model,operatingSystem,serialNumber,complianceState,lastSyncDateTime'
}

process {
# Escape only the string literal; keep OData filter syntax intact.
$escapedUpn = $UserPrincipalName.Replace("'", "''")
$filter = "userPrincipalName eq '$escapedUpn'"

# Ordered fallback chain matching the BadRequest resilience pattern used elsewhere.
$candidateUris = @(
"$baseUri`?`$filter=$filter&`$select=$select",
"$baseUri`?`$filter=$filter",
"$baseUri`?`$select=$select",
$baseUri
)

$resp = $null
$lastBadRequestError = $null

foreach ($candidateUri in $candidateUris) {
try {
$resp = Invoke-GraphGet -Uri $candidateUri
break
} catch {
$errorMessage = $_.Exception.Message
if ($errorMessage -match 'BadRequest|400') {
$lastBadRequestError = $_
Write-Verbose -Message "Managed device query returned BadRequest for URI '$candidateUri'. Trying next fallback."
continue
}

# Non-BadRequest errors are fatal; re-throw immediately.
throw
}
}

# All candidates failed with BadRequest; surface the last error.
if ($null -eq $resp -and $null -ne $lastBadRequestError) {
throw $lastBadRequestError
}

# Normalise response: unwrap .value collection or treat as single object.
$devices = @()
if ($null -ne $resp) {
if ($null -ne $resp.value) {
$devices = @($resp.value)
} else {
$devices = @($resp)
}
}

# Apply exact match locally to handle unfiltered fallback responses.
$matchedDevices = @($devices | Where-Object -FilterScript { [string]$_.userPrincipalName -ieq $UserPrincipalName })

if ($matchedDevices.Count -eq 0) {
Write-Verbose -Message "No managed devices found for userPrincipalName '$UserPrincipalName'."
return [PSCustomObject[]]@()
}

# Return managed device objects with fields required by downstream callers.
$matchedDevices | ForEach-Object -Process {
[PSCustomObject]@{
id = $_.id
deviceName = $_.deviceName
userPrincipalName = $_.userPrincipalName
manufacturer = $_.manufacturer
model = $_.model
operatingSystem = $_.operatingSystem
serialNumber = $_.serialNumber
complianceState = $_.complianceState
lastSyncDateTime = $_.lastSyncDateTime
}
}
} # Process
} # Cmdlet
Loading
Loading