Public/Get-IntuneLapsCredential.ps1

function Get-IntuneLapsCredential {
    <#
    .SYNOPSIS
        Retrieves LAPS local admin credentials for an Intune-managed device.
    .DESCRIPTION
        Calls the Microsoft Graph API endpoint:
            GET /directory/deviceLocalCredentials/{deviceId}
        Always fetches device metadata (deviceName, backup timestamps).
 
        When the current session has Full permission level, also fetches the full
        credentials array containing all local admin accounts and their passwords.
        All accounts are returned (sorted newest-first by backup date).
 
        When the session has Metadata level, only metadata is returned and a warning
        explains which gate is limiting access (scope or role).
 
        Use Connect-IntuneLaps to establish a session. The session's EffectiveLevel
        determines what is returned — there is no -IncludePassword parameter.
    .PARAMETER DeviceId
        The Entra device object ID (GUID). Accepts pipeline input from Find-IntuneLapsDevice.
    .PARAMETER DeviceName
        Device name or prefix to search for. Internally calls Find-IntuneLapsDevice and
        retrieves credentials for each matching device. Use -ExactMatch for an exact name.
    .PARAMETER ExactMatch
        When used with -DeviceName, requires an exact (case-insensitive) name match.
    .EXAMPLE
        Get-IntuneLapsCredential -DeviceId 'b465e4e8-e4e8-b465-e8e4-65b4e8e465b4'
    .EXAMPLE
        Get-IntuneLapsCredential -DeviceName 'DESKTOP-001' -ExactMatch
    .EXAMPLE
        Get-IntuneLapsCredential -DeviceName 'DESKTOP-'
    .EXAMPLE
        Find-IntuneLapsDevice -DeviceName 'WS001' | Get-IntuneLapsCredential
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByDeviceId')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(ParameterSetName = 'ByDeviceId', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DeviceId,

        [Parameter(ParameterSetName = 'ByDeviceName', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DeviceName,

        [Parameter(ParameterSetName = 'ByDeviceName', Mandatory = $false)]
        [switch]$ExactMatch
    )

    begin {
        $ErrorActionPreference = 'Stop'
        $Session = Get-CurrentSession
        $Session.AssertMinimumLevel([LapsPermissionLevel]::Metadata)
    }

    process {
        # Resolve -DeviceName to a list of device IDs via Find-IntuneLapsDevice
        [string[]]$ResolvedIds = if ($PSCmdlet.ParameterSetName -eq 'ByDeviceName') {
            $Found = Find-IntuneLapsDevice -DeviceName $DeviceName -ExactMatch:$ExactMatch
            if (-not $Found) { return }
            @($Found | ForEach-Object { $_.DeviceId })
        } else {
            @($DeviceId)
        }

        foreach ($ResolvedId in $ResolvedIds) {
            try {
                # Step 1: Always fetch metadata (no $select=credentials — avoids unnecessary privilege)
                [string]$MetadataUri = "https://graph.microsoft.com/v1.0/directory/deviceLocalCredentials/$ResolvedId"
                Write-Verbose "Fetching LAPS metadata for device: $ResolvedId"
                $MetadataResponse = Invoke-MgGraphRequestWithRetry -Parameters @{ Method = 'GET'; Uri = $MetadataUri }

                $Result = [PSCustomObject]@{
                    DeviceId           = $ResolvedId
                    DeviceName         = $MetadataResponse.deviceName
                    LastBackupDateTime = $MetadataResponse.lastBackupDateTime
                    RefreshDateTime    = $MetadataResponse.refreshDateTime
                    IsPasswordExpired  = if ($MetadataResponse.refreshDateTime) { [datetime]$MetadataResponse.refreshDateTime -lt (Get-Date) } else { $null }
                    EffectiveLevel     = $Session.EffectiveLevel
                    Credentials        = $null   # [PSCustomObject[]] when Full; $null when Metadata
                }

                # Step 2: Fetch credentials array only when session has Full access
                if ($Session.CanRetrievePasswords()) {
                    Write-Verbose 'Session has Full access — requesting credentials array...'
                    [string]$CredUri = 'https://graph.microsoft.com/v1.0/directory/deviceLocalCredentials/' + $ResolvedId + '?$select=id,credentials'

                    try {
                        $CredResponse = Invoke-MgGraphRequestWithRetry -Parameters @{ Method = 'GET'; Uri = $CredUri }

                        if ($CredResponse.credentials -and $CredResponse.credentials.Count -gt 0) {
                            # Return ALL accounts, sorted newest-first by backup date
                            $Result.Credentials = @(
                                $CredResponse.credentials |
                                Sort-Object { [datetime]$_.backupDateTime } -Descending |
                                ForEach-Object {
                                    [PSCustomObject]@{
                                        AccountName    = $_.accountName
                                        AccountSid     = $_.accountSid
                                        Password       = ConvertFrom-LapsPassword -PasswordBase64 $_.passwordBase64
                                        BackupDateTime = $_.backupDateTime
                                    }
                                }
                            )
                        }
                        # If credentials array is empty: device has a LAPS record but no credential entries yet
                    }
                    catch {
                        [int]$HttpStatus = 0
                        if ($_.Exception.PSObject.Properties['Response'] -and $null -ne $_.Exception.Response) {
                            $HttpStatus = [int]$_.Exception.Response.StatusCode
                        }
                        [bool]$Is403 = ($HttpStatus -eq 403) -or ($HttpStatus -eq 0 -and "$_" -match '\b403\b')

                        if ($Is403) {
                            [string]$AuHint = if ($Session.HasAuScopedRoles()) {
                                " Your role is AU-scoped — device '$($Result.DeviceName)' may be outside your Administrative Unit."
                            } else { '' }
                            Write-Warning "Credential retrieval returned 403 for device '$($Result.DeviceName)'. Effective level is '$($Session.EffectiveLevel)' but Graph denied access.$AuHint"
                        }
                        else {
                            Write-Error "Failed to retrieve LAPS credentials for device '$($Result.DeviceName)': $_"
                        }
                    }
                }
                else {
                    # Metadata-level session: explain why and provide actionable hints
                    [string]$Explanation = $Session.GetLimitingGateExplanation([LapsPermissionLevel]::Full)
                    Write-Warning "Metadata only — credentials not retrieved. $Explanation"

                    foreach ($Hint in $Session.GetPimUpgradeHints()) {
                        Write-Warning $Hint
                    }
                }

                $Result
            }
            catch {
                [int]$HttpStatus = 0
                if ($_.Exception.PSObject.Properties['Response'] -and $null -ne $_.Exception.Response) {
                    $HttpStatus = [int]$_.Exception.Response.StatusCode
                }
                [bool]$IsNotFound = ($HttpStatus -eq 404) -or
                                    ($HttpStatus -eq 400 -and "$_" -match 'could not be found') -or
                                    ($HttpStatus -eq 0 -and ("$_" -match '\b404\b' -or ("$_" -match '\b400\b' -and "$_" -match 'could not be found')))
                if ($IsNotFound) {
                    Write-Warning "No LAPS credential record found for device '$ResolvedId'. Ensure LAPS is configured and the device has checked in recently."
                }
                else {
                    Write-Error "Failed to retrieve LAPS credential for device '$ResolvedId': $_"
                }
            }
        }
    }
}