Public/Get/Get-OATHToken.ps1

<#
.SYNOPSIS
    Gets OATH hardware tokens from Microsoft Entra ID
.DESCRIPTION
    Retrieves OATH hardware tokens from Microsoft Entra ID via the Microsoft Graph API.
    Can filter by status, ID, serial number, or user assignment.
.PARAMETER TokenId
    The ID of a specific token to retrieve
.PARAMETER SerialNumber
    Filter tokens by serial number (can include wildcards)
.PARAMETER UserId
    Filter tokens assigned to a specific user ID or UPN
.PARAMETER Status
    Filter tokens by status (available, assigned, activated)
.PARAMETER IncludeAll
    Include all tokens regardless of status
.PARAMETER AvailableOnly
    Only include tokens that are available (not assigned)
.PARAMETER AssignedOnly
    Only include tokens that are assigned to users
.PARAMETER ActivatedOnly
    Only include tokens that are activated
.PARAMETER ApiVersion
    The Microsoft Graph API version to use. Defaults to 'beta'.
.EXAMPLE
    Get-OATHToken
    Gets all hardware OATH tokens in the tenant
.EXAMPLE
    Get-OATHToken -TokenId "00000000-0000-0000-0000-000000000000"
    Gets a specific token by ID
.EXAMPLE
    Get-OATHToken -SerialNumber "1234*"
    Gets all tokens with serial numbers starting with "1234"
.EXAMPLE
    Get-OATHToken -UserId "user@contoso.com"
    Gets all tokens assigned to the specified user
.EXAMPLE
    Get-OATHToken -AvailableOnly
    Gets only tokens that are available for assignment
.NOTES
    Requires Microsoft.Graph.Authentication module and appropriate permissions:
    - Policy.ReadWrite.AuthenticationMethod
    - Directory.Read.All
#>


function Get-OATHToken {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter(ParameterSetName = 'ById', Mandatory = $true)]
        [string]$TokenId,
        
        [Parameter(ParameterSetName = 'BySerial')]
        [string]$SerialNumber,
        
        [Parameter(ParameterSetName = 'ByUser')]
        [string]$UserId,
        
        [Parameter(ParameterSetName = 'ByStatus')]
        [ValidateSet('available', 'assigned', 'activated')]
        [string]$Status,
        
        [Parameter(ParameterSetName = 'All')]
        [switch]$IncludeAll,
        
        [Parameter(ParameterSetName = 'ByStatus')]
        [switch]$AvailableOnly,
        
        [Parameter(ParameterSetName = 'ByStatus')]
        [switch]$AssignedOnly,
        
        [Parameter(ParameterSetName = 'ByStatus')]
        [switch]$ActivatedOnly,
        
        [Parameter()]
        [string]$ApiVersion = "beta"
    )
    
    begin {
        # Initialize the skip processing flag at the start of each function call
        $script:skipProcessing = $false
        
        # Ensure we're connected to Graph
        if (-not (Test-MgConnection)) {
            $script:skipProcessing = $true
            # Return here only exits the begin block, not the function
            return
        }
        
        $baseEndpoint = "https://graph.microsoft.com/$ApiVersion/directory/authenticationMethodDevices/hardwareOathDevices"
    }
    
    process {
        # Skip all processing if the connection check failed
        if ($script:skipProcessing) {
            # Explicitly return null from the process block
            return $null
        }
        
        try {
            # Handle single token retrieval by ID
            if ($PSCmdlet.ParameterSetName -eq 'ById') {
                if (-not (Test-OATHTokenId -TokenId $TokenId)) {
                    Write-Error "Invalid token ID format"
                    return
                }
                
                $endpoint = "$baseEndpoint/$TokenId"
                $token = Invoke-MgGraphWithErrorHandling -Uri $endpoint
                
                # Transform and return token
                return [PSCustomObject]@{
                    PSTypeName = 'OATHToken'
                    Id = $token.id
                    SerialNumber = $token.serialNumber
                    DisplayName = $token.displayName
                    Manufacturer = $token.manufacturer
                    Model = $token.model
                    Status = $token.status
                    HashFunction = $token.hashFunction
                    TimeInterval = $token.timeIntervalInSeconds
                    LastUsed = if ($token.lastUsedDateTime) { [datetime]$token.lastUsedDateTime } else { $null }
                    AssignedToId = $token.assignedTo.id
                    AssignedToName = $token.assignedTo.displayName
                    AssignedToUpn = $token.assignedTo.userPrincipalName
                    Created = if ($token.createdDateTime) { [datetime]$token.createdDateTime } else { $null }
                    RawObject = $token
                }
            }
            else {
                # Handle user resolution if needed
                if ($UserId) {
                    $user = Get-MgUserByIdentifier -Identifier $UserId
                    if (-not $user) {
                        Write-Error "User not found: $UserId"
                        return
                    }
                    $UserId = $user.id
                }
                
                # Get all tokens first
                $tokens = Invoke-MgGraphWithErrorHandling -Uri $baseEndpoint
                
                # Filter by status if needed
                if ($Status -or $AvailableOnly -or $AssignedOnly -or $ActivatedOnly) {
                    $statusFilter = if ($Status) { $Status }
                                   elseif ($AvailableOnly) { 'available' }
                                   elseif ($AssignedOnly) { 'assigned' }
                                   elseif ($ActivatedOnly) { 'activated' }
                                   else { $null }
                    
                    if ($statusFilter) {
                        $tokens.value = $tokens.value | Where-Object { $_.status -eq $statusFilter }
                    }
                }
                
                # Filter by serial number if provided
                if ($SerialNumber) {
                    if ($SerialNumber -like "*[?*]*") {
                        # Wildcard search
                        $wildcardPattern = $SerialNumber -replace '\*', '.*' -replace '\?', '.'
                        $tokens.value = $tokens.value | Where-Object { $_.serialNumber -match "^$wildcardPattern$" }
                    }
                    else {
                        # Exact match
                        $tokens.value = $tokens.value | Where-Object { $_.serialNumber -eq $SerialNumber }
                    }
                }
                
                # Filter by user if provided
                if ($UserId) {
                    $tokens.value = $tokens.value | Where-Object { $_.assignedTo.id -eq $UserId }
                }
                
                # Transform and return tokens
                return $tokens.value | ForEach-Object {
                    [PSCustomObject]@{
                        PSTypeName = 'OATHToken'
                        Id = $_.id
                        SerialNumber = $_.serialNumber
                        DisplayName = $_.displayName
                        Manufacturer = $_.manufacturer
                        Model = $_.model
                        Status = $_.status
                        HashFunction = $_.hashFunction
                        TimeInterval = $_.timeIntervalInSeconds
                        LastUsed = if ($_.lastUsedDateTime) { [datetime]$_.lastUsedDateTime } else { $null }
                        AssignedToId = $_.assignedTo.id
                        AssignedToName = $_.assignedTo.displayName
                        AssignedToUpn = $_.assignedTo.userPrincipalName
                        Created = if ($_.createdDateTime) { [datetime]$_.createdDateTime } else { $null }
                        RawObject = $_
                    }
                }
            }
        }
        catch {
            Write-Error "Error retrieving OATH tokens: $_"
        }
    }
}

# Add type formatting for better console display
Update-TypeData -TypeName 'OATHToken' -DefaultDisplayPropertySet Id, SerialNumber, DisplayName, Status, AssignedToName -ErrorAction SilentlyContinue

# Add alias for backward compatibility - only if it doesn't already exist
if (-not (Get-Alias -Name 'Get-HardwareOathTokens' -ErrorAction SilentlyContinue)) {
    New-Alias -Name 'Get-HardwareOathTokens' -Value 'Get-OATHToken'
}