• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar

Clatent

Technology | Fitness | Food

  • About
  • Resources
  • Contact

The Blog

New EntraFIDOFinder Module Version Release!

May 25, 2026 by ClaytonT Leave a Comment

Good news and bad news.. which would you like first? Bad news? There are no new external PowerShell functions in this release, but I did create some functions to compare differences as well as one to show changes by the month. Is there a functionality you are looking for? Add an issue or discussion and lets make it happen!

Now for the changes, we have 11 new keys, 7 updated keys, and 52 keys removed.

✅ New Authenticators (11)

The following authenticators are now supported:

Clife Key 2

AAGUID: fc5ca237-69a0-4f3c-afe4-1ebc66def6df

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC❌
BLE❌

Clife Key 2 NFC

AAGUID: 23315ad0-6aca-4ba1-952e-f044f1e36976

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC✅
BLE❌

eToken FIDO NFC

AAGUID: b113a455-cfb6-4c17-8cba-cd952feb7d48

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

G+D StarKey FIDO2-NFC

AAGUID: 7a53c643-9dec-4219-b3a4-f9d24aca4e12

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC✅
BLE❌

HID Crescendo Key V3

AAGUID: 87c13177-85d6-40ac-8c61-fe7ab3de9dfb

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC✅
BLE❌

HID Crescendo 4000

AAGUID: 0b8b05a4-ebd4-4b0b-8f5f-33d7b6e606ab

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

SECORA ID V2 by Infineon Pay Edition

AAGUID: 3e9db280-256a-4e17-b08e-19d79e9be166

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

SECORA ID V2 by Infineon Pay Edition M

AAGUID: 005b20e1-f146-4b87-8f3a-36848ff60ea6

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

SECORA ID Key S USB by Infineon Consumer Edition

AAGUID: 9a272558-5cfa-4424-be37-65509677b77d

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

Taglio CTAP2.1 BIO

AAGUID: 0f00cc22-4640-41e7-9585-384ec73ffe9b

Supported Interfaces:

InterfaceSupported
Biometric✅
USB✅
NFC✅
BLE❌

Thales PAY GFCX13 authenticator

AAGUID: 04a8fcf2-19c1-457b-911e-69219f17583f

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC✅
BLE❌

⚠️ Updated Authenticators (7)

The following authenticators have been updated with new capability information:

Arculus FIDO 2.1 Key Card [P71]

AAGUID: 3f59672f-20aa-4afe-b6f4-7e5e916b6d98

Changes:

  • USB: ✅ → ❌
  • NFC: ❌ → ✅

Arculus FIDO2/U2F Key Card

AAGUID: 9d3df6ba-282f-11ed-a261-0242ac120002

Changes:

  • USB: ✅ → ❌
  • NFC: ❌ → ✅

GoTrust Idem Key FIDO2 Authenticator

AAGUID: 3b1adb99-0dfe-46fd-90b8-7f7614a4de2a

Changes:

  • USB: ❌ → ✅
  • NFC: ❌ → ✅

GoTrust Idem Key (Consumer profile)

AAGUID: c611b55c-77b2-4527-8082-590e931b2f08

Changes:

  • Description: ‘Idem Key (Consumer profile)’ → ‘GoTrust Idem Key (Consumer profile)’

IDEX CTAP2.1 Biometrics

AAGUID: 49a15c1c-3f63-3f51-23a7-b9e00096edd1

Changes:

  • Description: ‘IDEX CTAP2.1 Biometric, No pin’ → ‘IDEX CTAP2.1 Biometrics’
  • USB: ❌ → ✅

IDmelon Key

AAGUID: 39a5647e-1853-446c-a1f6-a79bae9f5bc7

Changes:

  • Description: ‘IDmelon Android Authenticator’ → ‘IDmelon Key’

IDmelon Authenticator

AAGUID: 820d89ed-d65a-409e-85cb-f73f0578f82a

Changes:

  • Description: ‘IDmelon iOS Authenticator’ → ‘IDmelon Authenticator’

❌ Removed Authenticators (52)

The following authenticators have been removed:

Android Authenticator

AAGUID: b93fd961-f2e6-462f-b122-82002247de78

Dapple Authenticator from Dapple Security Inc.

AAGUID: 6dae43be-af9c-417b-8b9f-1b611168ec60

Deepnet SafeKey/Classic (FP)

AAGUID: e41b42a3-60ac-4afb-8757-a98f2d7f6c9f

Deepnet SafeKey/Classic (USB)

AAGUID: b9f6b7b6-f929-4189-bca9-dd951240c132

ellipticSecure MIRkey USB Authenticator

AAGUID: eb3b131e-59dc-536a-d176-cb7306da10f5

Ensurity AUTH TouchPro

AAGUID: 50cbf15a-238c-4457-8f16-812c43bf3c49

Ensurity AUTH BioPro Desktop

AAGUID: 9eb85bb6-9625-4a72-815d-0487830ccab2

ESS Smart Card Inc. Authenticator

AAGUID: 5343502d-5343-5343-6172-644649444f32

eToken Fusion NFC PIV Enterprise

AAGUID: c3f47802-de73-4dfc-ba22-671fe3304f90

eWBM eFA500 FIDO2 Authenticator

AAGUID: 361a3082-0278-4583-a16f-72a527f973e4

Excelsecu eSecu FIDO2 PRO+ Security Key

AAGUID: f573f209-b7fb-b261-671a-d7cf624cc812

Feitian ePass FIDO2-NFC Plus Authenticator

AAGUID: 260e3021-482d-442d-838c-7edfbe153b7e

Feitian FIDO Smart Card

AAGUID: 2c0df832-92de-4be1-8412-88a8f074df4a

GoldKey Security Token

AAGUID: 0db01cd6-5618-455b-bb46-1ec203d3213e

Hideez Key 3 FIDO2

AAGUID: 3e078ffd-4c54-4586-8baa-a77da113aec5

Ideem ZSM FIDO2 Authenticator

AAGUID: 5e264d9d-28ef-4d34-95b4-5941e7a4faa8

IDEMIA SOLVO Fly 80 R1 FIDO Card Draft

AAGUID: 3fd410dc-8ab7-4b86-a1cb-c7174620b2dc

KeyVault Secp256R1 FIDO2 CTAP2 Authenticator

AAGUID: d61d3b87-3e7c-4aea-9c50-441c371903ad

Ledger Flex FIDO2 Authenticator

AAGUID: 1d8cac46-47a1-3386-af50-e88ae46fe802

Ledger Nano S Plus FIDO2 Authenticator

AAGUID: 58b44d0b-0a7c-f33a-fd48-f7153c871352

Ledger Nano X FIDO2 Authenticator

AAGUID: fcb1bcb4-f370-078c-6993-bc24d0ae3fbe

Ledger Stax FIDO2 Authenticator

AAGUID: 6e24d385-004a-16a0-7bfe-efd963845b34

Ledger Nano S FIDO2 Authenticator

AAGUID: 341e4da9-3c2e-8103-5a9f-aad887135200

OCTATCO EzQuant FIDO2 AUTHENTICATOR

AAGUID: bc2fe499-0d8e-4ffe-96f3-94a82840cf8c

OneKey FIDO2 Bluetooth Authenticator

AAGUID: 70e7c36f-f2f6-9e0d-07a6-bcc243262e6b

OneKey FIDO2 Authenticator

AAGUID: 69e7c36f-f2f6-9e0d-07a6-bcc243262e6b

Pone Biometrics OFFPAD Authenticator

AAGUID: 09591fc6-9811-48f7-8f57-b9f23df6413f

Samsung Pass

AAGUID: 53414d53-554e-4700-0000-000000000000

ToothPic Passkey Provider

AAGUID: cc45f64e-52a2-451b-831a-4edd8022a202

TruU Windows Authenticator

AAGUID: ba86dc56-635f-4141-aef6-00227b1b9af6

TruU Windows Authenticator

AAGUID: 95e4d58c-056e-4a65-866d-f5a69659e880

TruU FIDO2 Authenticator

AAGUID: bb878d7b-cf54-4784-b390-357030497043

FIDO Alliance TruU Sample FIDO2 Authenticator

AAGUID: ca87cb70-4c1b-4579-a8e8-4efdd7c007e0

USB/NFC Passcode Authenticator

AAGUID: cfcb13a2-244f-4b36-9077-82b79d6a7de7

TEST (DUMMY RECORD)

AAGUID: ab32f0c6-2239-afbb-c470-d2ef4e254db6

Veridium iOS SDK

AAGUID: 6e8d1eae-8d40-4c25-bcf8-4633959afc71

Veridium Android SDK

AAGUID: 5ea308b2-7ac7-48b9-ac09-7e2da9015f8c

VivoKey Apex FIDO2

AAGUID: d7a423ad-3e19-4492-9200-78137dccc136

WinMagic FIDO Eazy – TPM

AAGUID: 970c8d9c-19d2-46af-aa32-3f448db49e35

WinMagic FIDO Eazy – Software

AAGUID: 31c3f7ff-bf15-4327-83ec-9336abcbcd34

WinMagic FIDO Eazy – Phone

AAGUID: f56f58b3-d711-4afc-ba7d-6ac05f88cb19

WiSECURE Blentity FIDO2 Authenticator

AAGUID: 5753362b-4e6b-6345-7b2f-255438404c75

YubiKey 5 FIPS Series with Lightning Preview

AAGUID: 5b0e46ba-db02-44ac-b979-ca9b84f5e335

YubiKey 5 FIPS Series with Lightning (RC Preview)

AAGUID: 9e66c661-e428-452a-a8fb-51f7ed088acf

YubiKey 5 FIPS Series with NFC (RC Preview)

AAGUID: ce6bf97f-9f69-4ba7-9032-97adc6ca5cf1

YubiKey 5 FIPS Series (RC Preview)

AAGUID: d2fbd093-ee62-488d-9dad-1e36389f8826

Security Key NFC by Yubico Preview

AAGUID: 760eda36-00aa-4d29-855b-4012a182cdeb

YubiKey 5 Series with Lightning Preview

AAGUID: 3124e301-f14e-4e38-876d-fbeeb090e7bf

YubiKey 5 Series with NFC Preview

AAGUID: 34f5766d-1536-4a24-9033-0e294e510fb0

Security Key NFC by Yubico – Enterprise Edition Preview

AAGUID: 2772ce93-eb4b-4090-8b73-330f48477d73

YubiKey 5 FIPS Series with NFC Preview

AAGUID: 62e54e98-c209-4df3-b692-de71bb6a8528

ZTPass SmartAuth

AAGUID: 99bf4610-ec26-4252-b31f-7380ccd59db5

That’s it! How are you using EntraFIDOFinder? Would love to hear about it and any feedback.

Download at:
PowerShell Gallery: https://www.powershellgallery.com/packages/EntraFIDOFinder/0.0.23
GitHub: https://github.com/DevClate/EntraFIDOFinder

Use the web version at: https://devclate.github.io/EntraFIDOFinder/Explorer/

Have a great day!

Tagged With: 365, Automation, Entra, EntraFIDOFinder, FIDO2, PowerShell, Security

Did you know there is a Giphy rating in Teams? Custom Maester Tests save the day

May 1, 2026 by ClaytonT Leave a Comment

Now let me ask you, how secure is your Microsoft Teams? When was the last time you looked at how it was configured? While working on another side project I had the great idea of converting some of the work to Maester tests to help others have a quick look at how their Teams is configured. These tests aren’t the end all be all, but there are 45 tests to make your Microsoft Teams more secure. If you only copy them into your custom tests folder, please make sure to review why they passed or failed, as some are for awareness until I create exact tests for them.

I will be creating more, but thought this would be a great start for many so you can see what is possible and customize them to your organization. Hopefully, you will share yours as well to help others make their Teams more secure.

How to use them? All you have to do is copy the 3 files for each test into your Custom Tests folder, then use a “Find and Replace” for “Contoso” and put in your company name. If you don’t want to change any of the expected values, you are good to go to start running the tests. If you just want to see these new tests you can run Invoke-Maester -Tag “TEAMS.TC.*” and it will only show you these new tests.

Here are some of the tests and there is a link to the repo down below

Anonymous Join Meeting – Anonymous users should not be allowed to join meetings
Anonymous Start Meeting – Anonymous users should not be allowed to start meetings
Anonymous Dial Out – Anonymous users should not be allowed to dial out to prevent toll fraud
App Sideloading – App sideloading should be disabled to enforce security review
Auto Admitted Users – Auto-admitted users should be restricted to prevent unauthorized access
Broadcast transcription settings should be reviewed
Call forwarding to phone should be reviewed
Channel meeting scheduling should be controlled
Broadcast recording settings should be reviewed
Chat settings should protect sensitive data
Broadcast attendee visibility should protect privacy
Chat permission roles should be reviewed
Email into Channel – Email into channel should be disabled to prevent bypassing email security controls
External collaboration should be configured with security controls
External non-trusted meeting chat should be disabled
External participants should not be allowed to give or request control
Federation should be restricted to specific allowed domains
Giphy content rating should be set to Strict if enabled
Guest IP video settings should be reviewed
Guest meeting chat settings should be reviewed
Guests should not be able to start ad-hoc meetings
Guest screen sharing should be limited to prevent data leakage
Guests should not control transcription
URL previews should be disabled to prevent information leakage

Let me know what you think, feel free to create an issue if their is a test you would like to see and/or if you find any issues in the tests.

And for those wondering, the giphy ratings are strict, moderate, or turned off.

Tests Repo: https://github.com/DevClate/Custom-Maester-Tests/tree/main/tests/Teams/Configuration
Web Page: https://devclate.github.io/Custom-Maester-Tests/docs/intro/

Keep sharing, stay secure, and have a great day!

Tagged With: 365, Automation, Maester, PowerShell, Read-Only Friday, Reporting, Security, Teams

Learning ValidateSet in PowerShell: Valid Values Only

March 26, 2026 by ClaytonT Leave a Comment

Introduction

I know your wondering, why isn’t he writing about AI, and just talking about ValidateSet? If your in the early stages of your PowerShell journey, you may not know about ValidateSet and it is a great feature to know and to make sure your functions have it that you create. Have you ever written or used a PowerShell function where users could enter any value, only to have your script fail because they typed “prod” instead of “Production”? Or maybe they entered “DEvelopment” with creative capitalization? This is where ValidateSet becomes your best friend.

ValidateSet is a parameter attribute that restricts parameter values to a predefined set of options. Think of it as a dropdown menu for your PowerShell functions as users can only choose from the options you provide. Also it can save you big time, if you put an invalid value in, and it partially runs while not having the right checks and balances in, it could do a lot of damage. If you have ValidateSet in place, it won’t let an invalid value be inputted.

Why Use ValidateSet?

Before we dive into the how, let’s understand the why:

  1. Error Prevention: Stop invalid values before they cause problems
  2. User Guidance: Users see exactly what options are available
  3. Tab Completion: PowerShell automatically provides tab completion for your valid options
  4. Self-Documenting Code: The valid options are visible in your function definition
  5. Consistency: Ensures everyone uses the same values across your organization

Now, let’s build your ValidateSet skills step by step!


Level 1: Basic ValidateSet

Let’s start with the simplest example. Imagine you’re creating a function to restart a service, but only for specific environments:

function Restart-AppService {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Development', 'Testing', 'Production')]
        [string]$Environment
    )

    Write-Host "Restarting service in $Environment environment..." -ForegroundColor Green
    # Your restart logic here
}

What’s happening here?

  • The [ValidateSet()] attribute limits $Environment to exactly three values
  • If someone tries Restart-AppService -Environment "Staging", PowerShell will throw an error
  • Users can press TAB after typing -Environment to cycle through valid options

Try it yourself:

# This works ✓
Restart-AppService -Environment Development

# This fails ✗
Restart-AppService -Environment Staging
# Error: Cannot validate argument on parameter 'Environment'

Level 2: Case Sensitivity and Tab Completion

One beautiful feature of ValidateSet is that it’s case-insensitive by default:

function Set-LogLevel {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Verbose', 'Info', 'Warning', 'Error', 'Critical')]
        [string]$Level
    )

    Write-Host "Log level set to: $Level"
}

# All of these work!
Set-LogLevel -Level verbose      # ✓
Set-LogLevel -Level VERBOSE      # ✓
Set-LogLevel -Level VeRbOsE      # ✓
Set-LogLevel -Level Verbose      # ✓

The Magic of Tab Completion:

When you type Set-LogLevel -Level and press TAB, PowerShell cycles through:

  • Verbose → Info → Warning → Error → Critical → Verbose → …

This significantly improves user experience and reduces typing errors!


Level 3: Multiple Parameters with ValidateSet

Real-world functions often need multiple validated parameters. Let’s build upon our knowledge:

function Deploy-Application {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Development', 'Testing', 'Staging', 'Production')]
        [string]$Environment,

        [Parameter(Mandatory)]
        [ValidateSet('East', 'West', 'Central', 'Europe', 'Asia')]
        [string]$Region,

        [Parameter()]
        [ValidateSet('IIS', 'Apache', 'Nginx')]
        [string]$WebServer = 'IIS'
    )

    Write-Host "Deploying to $Environment environment in $Region region using $WebServer" -ForegroundColor Cyan
}

Key Points:

  • Each parameter has its own ValidateSet
  • You can combine ValidateSet with default values ($WebServer = 'IIS')
  • The default value must be one of the valid set values
# Example usage
Deploy-Application -Environment Production -Region East
# Output: Deploying to Production environment in East region using IIS

Level 4: Dynamic ValidateSet

Here’s where things get exciting! What if your valid options need to change based on your environment or context? Enter dynamic ValidateSet using classes. Make sure your .txt file only has 1 value per row with no commas, or it won’t read it correctly.

# First, create a class that implements IValidateSetValuesGenerator
class EnvironmentNamesGenerator : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        # This could read from a config file, database, or API
        $environments = Get-Content "C:\Config\environments.txt" -ErrorAction SilentlyContinue
        if ($environments) {
            return $environments
        }
        # Fallback to default values
        return @('Development', 'Testing', 'Production')
    }
}

function Connect-ToEnvironment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet([EnvironmentNamesGenerator])]
        [string]$Environment
    )

    Write-Host "Connecting to $Environment..." -ForegroundColor Green
}

Why is this powerful?

  • Valid values can come from files, databases, APIs, or Active Directory
  • Values update without modifying the function
  • Tab completion still works!

Level 5: ValidateSet with Arrays

Sometimes users need to select multiple values. ValidateSet works beautifully with arrays:

function Install-Features {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('WebServer', 'Database', 'MessageQueue', 'Cache', 'Monitoring')]
        [string[]]$Features
    )

    foreach ($feature in $Features) {
        Write-Host "Installing $feature..." -ForegroundColor Yellow
        # Installation logic here
    }
}

# Usage - multiple valid values
Install-Features -Features WebServer, Database, Cache

# Each value is still validated
Install-Features -Features WebServer, InvalidFeature  # ✗ Will fail

Level 6: Combining ValidateSet with Other Validators

You can stack multiple validation attributes for robust parameter validation:

function New-ServerName {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('DEV', 'TEST', 'PROD')]
        [string]$Environment,

        [Parameter(Mandatory)]
        [ValidateSet('WEB', 'DB', 'APP', 'API')]
        [string]$Type,

        [Parameter(Mandatory)]
        [ValidateRange(1, 999)]
        [int]$Number
    )

    $serverName = "{0}-{1}-{2:D3}" -f $Environment, $Type, $Number
    Write-Host "Server name: $serverName" -ForegroundColor Green
    return $serverName
}

# Usage
New-ServerName -Environment PROD -Type WEB -Number 42
# Output: PROD-WEB-042

Level 7: Real-World Enterprise Example

Let’s combine everything we’ve learned into a practical, enterprise-ready function:

# Dynamic validator for Azure regions
class AzureRegionsGenerator : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        # Get actual Azure locations (in real scenario)
        # For demo, we'll use static list
        return @('eastus', 'westus', 'centralus', 'northeurope', 'westeurope', 'southeastasia')
    }
}

function New-AzureResource {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Development', 'Testing', 'Staging', 'Production')]
        [string]$Environment,

        [Parameter(Mandatory)]
        [ValidateSet([AzureRegionsGenerator])]
        [string]$Location,

        [Parameter(Mandatory)]
        [ValidateSet('Basic', 'Standard', 'Premium')]
        [string]$Tier,

        [Parameter()]
        [ValidateSet('Windows', 'Linux')]
        [string]$OS = 'Windows',

        [Parameter()]
        [ValidateSet('Small', 'Medium', 'Large')]
        [string[]]$Features = @('Small')
    )

    # Generate resource name
    $resourceName = "rg-$Environment-$Location-$(Get-Date -Format 'yyyyMMdd')".ToLower()

    if ($PSCmdlet.ShouldProcess($resourceName, "Create Azure Resource")) {
        Write-Host "`nCreating Azure Resource:" -ForegroundColor Cyan
        Write-Host "  Name: $resourceName" -ForegroundColor White
        Write-Host "  Location: $Location" -ForegroundColor White
        Write-Host "  Tier: $Tier" -ForegroundColor White
        Write-Host "  OS: $OS" -ForegroundColor White
        Write-Host "  Features: $($Features -join ', ')" -ForegroundColor White

        # Your actual Azure creation logic here
        return $resourceName
    }
}

# Usage examples
New-AzureResource -Environment Production -Location eastus -Tier Premium
New-AzureResource -Environment Development -Location westeurope -Tier Basic -OS Linux -Features Small, Medium

Common Mistake to Avoid

Forgetting ValidateSet Creates Strings

# The parameter is always a string, even if it looks like a number
function Set-Priority {
    param (
        [ValidateSet('1', '2', '3')]
        [string]$Priority  # Note: string type
    )

    # Need to convert if doing numeric operations
    $numPriority = [int]$Priority
}

Conclusion

ValidateSet is a powerful, user-friendly feature that makes your PowerShell functions more robust and easier to use. Start with basic static sets, and as your needs grow, use dynamic ValidateSet with classes. I will say I haven’t used dynamic ValidateSet a much more than a few with no issues, but have heard to only use it when necessary. Maybe I’ll do a deep dive into it at sompoint.

Remember:

  • ✓ Always use ValidateSet when there’s a limited, known set of valid values
  • ✓ Leverage tab completion to improve user experience
  • ✓ Use dynamic ValidateSet for values that change frequently
  • ✓ Combine with other validators for bulletproof parameters
  • ✓ Document your choices with comments

Happy scripting! 🚀


Practice Exercise

Try creating this function yourself:

# Create a function that:
# 1. Takes a ComputerName parameter (mandatory)
# 2. Takes an Action parameter that validates against: 'Restart', 'Shutdown', 'LogOff'
# 3. Takes an optional Force parameter (boolean)
# 4. Writes what action would be performed

Below is one way to do it, but try yourself first!

```powershell
# Solution:
function Invoke-RemoteAction {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [Parameter(Mandatory)]
        [ValidateSet('Restart', 'Shutdown', 'LogOff')]
        [string]$Action,

        [Parameter()]
        [bool]$Force = $false
    )

    $forceText = if ($Force) { "forcefully" } else { "gracefully" }
    Write-Host "Would $Action $ComputerName $forceText" -ForegroundColor Yellow
}

Now take what you’ve learned here and make those functions more secure and easier to use!

What do you think of this style of blog post for learning concepts? Are you strongly for or against it? Let me know and hope you learned something from this blog post and please keep learning and sharing.

Tagged With: Automation, Basics, PowerShell, ValidateSet

Teams Chat and PowerShell – How to add value!

March 23, 2026 by ClaytonT Leave a Comment

It’s been a bit for a technical blog post, but I’ve missed it and finally found some time to do one. What we are learning today is how to send messages, photos, urls, and information from APIs to Teams’ chat using PowerShell. This only covers for Teams chat’s as using Teams’ channels use slightly different authentication and cmdlets, but the concepts are the same. I know it’s not as exciting as Agents, Identity, or zero trust, but this will hopefully make your work day easier and bring more value to your and your company.

To start this off, we will first need a chat ID. I recommend at first using your chat with yourself, or use a chat that only you and people that won’t mind being bombed with test messages will mind!

Best way to get your chat ID

  1. Go to the web version of Teams and go to the chat you want to Read and/or write to
    1. We use to be able to copy the url and pull the ChatID out of it, but I’m not seeing that option anymore.
  2. Then click on the 3 dots in the top right and click “Copy Link”
  3. Here we will need to use everything after “chat/” or “channel” and before “/conversations” or “/General”
    1. It usually starts with “19:” or “19%” right after the “chat/”
  4. Once we have that I’d save it to a variable so you don’t have to keep remembering it and or copying and pasting that large link. Here I saved it to the variable $MyTestChatID so it was easy to remember. If this was prod, I’d use something on the lines of $TeamsChatId
    $MyTestChatID = "19:6h632y7d-nmx8-1yno-325h-77en2mx84x83_s78hy6e7-n6g3-7 9j0-36hf-86njyio53053@unq.gbl.spaces"
  5. Now we need to connect to Microsoft Graph. If your just adding information to a Teams Chat, all you will need is ChatMessage.Send and Chat.ReadWrite, but if you are creating a new chat, then you will also need Chat.Create.Connect-MgGraph -Scopes "Chat.ReadWrite", "ChatMessage.Send"

Before we start digging in, one thing to know is there are 2 different types of “Content Type.” The first is “text” which only allows text formatting, and there is “html” which you guessed it, allows html formatting. I haven’t gone crazy with the html formatting but you can make some nice charts and such with it and use images.

Simple Text:

Here is a simple text example, that just puts “This is my test” in the chat that you earlier added.

$Content = "This is my test"
	 
$ChatMessageBody = @{
   contentType = 'text'
   content     = $Content
}
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody

Using Emojis and Text:

You can add some flare to the text contentType by using Emojis.This will add the caution icon to your message

$Content = "⚠️ ALERT: Production Server DB-01 is offline!"

	 
$ChatMessageBody = @{
   contentType = 'text'
   content     = $Content
}
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody
	

Using bold and italicize:

If we want to add anymore style we need to change the contentType to html. Now we can bold and italicize the alert.

$Content = "<b><i>⚠️ ALERT:</b></i> Production Server DB-01 is offline!<p>"

	 
$ChatMessageBody = @{
   contentType = 'html'
   content     = $Content
} 
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody

Using html:

Now lets add on to it and make the alert a bit more useful, like giving a link to the change log request form to see if there was any scheduled maintenance for this time.

$Content = "<b><i>⚠️ ALERT:</b></i> Production Server DB-01 is offline!<p>"
$Content = $Content + "Please confim if this is an issue"
$Content = $Content + "<p>For more information, see <a href='<https://corporate.com/changelog>'>Corporate Change Log Requests</a>"
	 
$ChatMessageBody = @{
  contentType = 'html'
  content     = $Content
} 
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody

Piping functions with html:

Lets go a little more advanced and pipe in a function and put the results in teams. In this example we get the results from Get-Process, and only show the Name, Id, and CPU usage. How this works is by running Get-Process, selecting those objects, then converting the output to fragmented HTML. This is where instead of showing all of the html code, it only shows it for the table. Then pipe it to Out-String to turn the string array into a single string so that Teams can read it. If you don’t it will output but it will output System.Object[].

$FunctionTest = Get-Process | Select-Object Name, Id, CPU
$Content = ($FunctionTest | ConvertTo-Html -Fragment) | Out-String

$ChatMessageBody = @{
    contentType = 'html'
    content     = $Content
}
	 
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody

Yes, here is a one-liner for it, but wanted to show you could have functions before the actual “$Content” if you wanted.

$Content = Get-Process |
Select Name, Id, CPU |
ConvertTo-Html -Fragment |
Out-String

$ChatMessageBody = @{
    contentType = 'html'
    content     = $Content
}
	 
$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody

APIs and html:

Now seeing text, html, and being able to show results from functions is great, but what about APIs? We can integrate them too, I mean who doesn’t want a Dad Joke every day right in there teams chat?? I tried to get Steven’s Judd API, but he wouldn’t give it to me. This is just scratching the surface, but wanted to show you it is possible. Full example right after the explaination.

A quick breakdown of this is it will first try what’s in the “try” block and if there is a terminating error at any part of it, it goes directly to the “catch” block and will run that code. Since we want to get information from icanhazdadjoke api, we have to first put the headers in, then call or “Invoke-RestMethod” the api with its Uri, and this grabs the information and saves it to the $response variable. Now we can use $($response.joke) to grab the actual joke for the example which we will see in the next step.

# Setting up headers and calling API for joke information
try {
    # Get a random dad joke from the API
    $headers = @{
        'Accept' = 'application/json'
        'User-Agent' = 'PowerShell Script (<https://github.com/DevClate/>)'
    }
    
    $response = Invoke-RestMethod -Uri "<https://icanhazdadjoke.com/>" -Headers $headers -Method Get
 

Here we create the Body content with “$jokeContent” as a Here-String(everything between @” and “@) which is a multi-line string literal that preserves all whitespace and line breaks. This makes the design of html easy and ensures it looks like we want in the teams chat. You can see we used a subexpresssion $($response.joke) to pull the actual joke, because if we just used $response.joke it won’t interpolate correctly inside the string.

 $jokeContent = @"
🤣 <b>Daily Dad Joke</b> 🤣<br><br>
<img src="<https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif>" alt="Dad Joke GIF" style="max-width: 200px; height: auto;"><br><br>
$($response.joke)<br><br>
<small><i>Joke ID: $($response.id)</i></small><br>
<small>Source: icanhazdadjoke.com | $(Get-Date -Format 'MMM dd, yyyy h:mm tt')</small>
"@

If your not familiar with html formatting, you can see the <b> that starts the bolding of “Daily Dad Joke” and stops bolding with </b>. Since this is a string we are using <br><br> to create line breaks.

🤣 <b>Daily Dad Joke</b> 🤣<br><br>

Here we will put in the code to show the image we want to show with the joke with the width and height.

<img src="<https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif>" alt="Dad Joke GIF" style="max-width: 200px; height: auto;"><br><br>

Also you can see with $(Get-Date -Format ‘MMM dd, yyyy h:mm tt’) to pull the date and time the joke was from.

<small>Source: icanhazdadjoke.com | $(Get-Date -Format 'MMM dd, yyyy h:mm tt')</small>

Then as long as there are no terminating errors you will get to the send your new daily joke to teams part, where you will add your $ChatMessageBody and for the content you would use $jokeContent unless you changed the variable for the joke content. Basically this makes it readable to Graph and Teams.

# Send to Teams body
    $ChatMessageBody = @{
    contentType = 'html'
    content     = $jokeContent
    }

After you’ve built the body, now we send it to Teams and output status to our terminal. Make sure you are using the correct ChatId, as you don’t want to send your message to the wrong person, so in this case, lets use our variable we created earlier $MyTestChatID. Then we will add our Body variable to make sure we get the right joke sent with $ChatMesssageBody. As long as all went smoothly we will see the joke in teams, and in our terminal we will see that it was successfully sent and the text version of the joke.

Now if it has a terminating error at any point, we move straight to the “Catch” so this is what the user will see if there is an terminating error. This is basically the same as above, except we add the “Write-Error” with the exception message, and put it a static joke in that gets sent to Teams. As you can see we changed the joke variable to $fallbackJoke and changed the content to $fallbackContent and everything else remains the same.

$ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody
    
    Write-Host "✅ Dad joke sent to Teams chat successfully!"
    Write-Host "Joke: $($response.joke)"
catch {
    Write-Error "❌ Failed to get dad joke: $($_.Exception.Message)"
    
    # Fallback joke if API fails
    $fallbackJoke = "Why don't scientists trust atoms? Because they make up everything!"
    
    $fallbackContent = @"
🤣 <b>Dad Joke (Backup)</b> 🤣<br><br>
<img src="<https://media.giphy.com/media/xT9IgG50Fb7Mi0prBC/giphy.gif>" alt="Classic Dad Joke" style="max-width: 200px; height: auto;"><br><br>
$fallbackJoke<br><br>
<small><i>API was unavailable, so here's a classic!</i></small><br>
<small>$(Get-Date -Format 'MMM dd, yyyy h:mm tt')</small>
"@
    
    $ChatMessageBody = @{
    contentType = 'html'
    content     = $fallbackContent
    }

    $ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody
    
    Write-Host "✅ Fallback dad joke sent to Teams chat!"
}

And here is the full script!

try {
    # Get a random dad joke from the API
    $headers = @{
        'Accept' = 'application/json'
        'User-Agent' = 'PowerShell Script (<https://github.com/DevClate/>)'
    }
    
    $response = Invoke-RestMethod -Uri "<https://icanhazdadjoke.com/>" -Headers $headers -Method Get
    
    # Using HTML img tag with external URL
    $jokeContent = @"
🤣 <b>Daily Dad Joke</b> 🤣<br><br>
<img src="<https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif>" alt="Dad Joke GIF" style="max-width: 200px; height: auto;"><br><br>
$($response.joke)<br><br>
<small><i>Joke ID: $($response.id)</i></small><br>
<small>Source: icanhazdadjoke.com | $(Get-Date -Format 'MMM dd, yyyy h:mm tt')</small>
"@

    # Send to Teams
    $ChatMessageBody = @{
    contentType = 'html'
    content     = $jokeContent
    }
    
    $ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody
    
    Write-Host "✅ Dad joke sent to Teams chat successfully!"
    Write-Host "Joke: $($response.joke)"
}
catch {
    Write-Error "❌ Failed to get dad joke: $($_.Exception.Message)"
    
    # Fallback joke if API fails
    $fallbackJoke = "Why don't scientists trust atoms? Because they make up everything!"
    
    $fallbackContent = @"
🤣 <b>Dad Joke (Backup)</b> 🤣<br><br>
<img src="<https://media.giphy.com/media/xT9IgG50Fb7Mi0prBC/giphy.gif>" alt="Classic Dad Joke" style="max-width: 200px; height: auto;"><br><br>
$fallbackJoke<br><br>
<small><i>API was unavailable, so here's a classic!</i></small><br>
<small>$(Get-Date -Format 'MMM dd, yyyy h:mm tt')</small>
"@
    
    $ChatMessageBody = @{
    contentType = 'html'
    content     = $fallbackContent
    }

    $ChatMessage = New-MgChatMessage -ChatId $MyTestChatID -Body $ChatMessageBody
    
    Write-Host "✅ Fallback dad joke sent to Teams chat!"
}

Advanced Example:

This script below is to show what is possible, and I won’t be going into detail on it, but wanted to give you a good starting point for a mini project. If you have any questions on it or want to work through it, I’d be glad to help out.

It will allow you to show completed and non completed migration status that you can sort by migration status and/or location status. If you wanted to, you could add a filter for department too. The only thing out of the box you need to do is put your ChatID in there and make sure you have the csv file saved.

I may have put in there a way to only have to put the users UPN to send them a message…

<#
.SYNOPSIS
    Get migration status from CSV and optionally send to Teams.

.DESCRIPTION
    Reads a CSV file containing user migration data and filters by location
    and migration status. Optionally sends the results to a Teams chat.

.EXAMPLE
    # All migrated users, all locations
    .\Get-MigrationStatus.ps1 -MigrationStatus migrated

.EXAMPLE
    # Not migrated users, specific location
    .\Get-MigrationStatus.ps1 -MigrationStatus notmigrated -Location Location1

.EXAMPLE
    # Both status, specific location, send to Teams
    .\Get-MigrationStatus.ps1 -MigrationStatus both -Location Location2 -RecipientUpn "manager@contoso.com"

.EXAMPLE
    # All users, all locations, to default chat
    .\Get-MigrationStatus.ps1 -MigrationStatus both

.NOTES
    Business Use Case: Migration status reporting, IT compliance tracking
    Required Scopes (if sending to Teams): Chat.Create, ChatMessage.Send
#>

param(
    [Parameter(Mandatory = $true)]
    [ValidateSet("Migrated", "NotMigrated", "Both")]
    [string]$MigrationStatus,

    [Parameter(Mandatory = $false)]
    [ValidateSet("Location1", "Location2", "Location3", "All")]
    [string]$Location = "All",

    [Parameter(Mandatory = $false)]
    [string]$RecipientUpn,

    [Parameter(Mandatory = $false)]
    [string]$ChatId = "default-chat-id",

    [Parameter(Mandatory = $false)]
    [string]$CsvPath = "$PSScriptRoot\\users.csv"
)

function Get-MigrationData {
    param(
        [string]$Path,
        [string]$Location,
        [string]$MigrationStatus
    )

    if (-not (Test-Path $Path)) {
        throw "CSV file not found: $Path"
    }

    $users = Import-Csv $Path

    # Filter by location
    if ($Location -ne "All") {
        $users = $users | Where-Object { $_.Location -eq $Location }
    }

    # Filter by migration status
    switch ($MigrationStatus) {
        "migrated" {
            $filtered = $users | Where-Object { $_.Migrated -ne "" -and $null -ne $_.Migrated }
        }
        "notmigrated" {
            $filtered = $users | Where-Object { $_.Migrated -eq "" -or $null -eq $_.Migrated }
        }
        "both" {
            $filtered = $users
        }
    }

    # Calculate counts
    $totalCount = ($users | Measure-Object).Count
    $migratedCount = ($users | Where-Object { $_.Migrated -ne "" -and $null -ne $_.Migrated } | Measure-Object).Count
    $pendingCount = $totalCount - $migratedCount

    return @{
        Users = $filtered
        TotalCount = $totalCount
        MigratedCount = $migratedCount
        PendingCount = $pendingCount
    }
}

function Send-MigrationStatusToTeams {
    param(
        [object]$Data,
        [string]$Location,
        [string]$MigrationStatus,
        [string]$RecipientUpn,
        [string]$ChatId
    )

    # Build HTML table
    $tableRows = ""
    foreach ($user in $Data.Users) {
        $migratedDisplay = if ($user.Migrated) { $user.Migrated } else { "<em>Pending</em>" }
        $tableRows += @"
<tr>
    <td>$($user.Name)</td>
    <td>$($user.Email)</td>
    <td>$($user.Location)</td>
    <td>$($user.Department)</td>
    <td>$migratedDisplay</td>
</tr>
"@
    }

    # Build title based on status and location
    $statusTitle = switch ($MigrationStatus) {
        "Migrated" { "Migrated" }
        "NotMigrated" { "Not Migrated" }
        "Both" { "All" }
    }

    $locationTitle = if ($Location -eq "All") { "All Locations" } else { $Location }

    $htmlContent = @"
<h2>📊 Migration Status - $statusTitle ($locationTitle)</h2>
<table border="1" cellpadding="5">
<tr>
    <th>Name</th>
    <th>Email</th>
    <th>Location</th>
    <th>Department</th>
    <th>Migrated</th>
</tr>
$tableRows
</table>
<h3>📈 Summary</h3>
<ul>
<li><strong>Total:</strong> $($Data.TotalCount)</li>
<li><strong>Migrated:</strong> $($Data.MigratedCount)</li>
<li><strong>Pending:</strong> $($Data.PendingCount)</li>
</ul>
<p><em>Report generated: $(Get-Date -Format "MMMM dd, yyyy HH:mm")</em></p>
"@

    # Check if already connected to Graph
    $graphContext = Get-MgContext -ErrorAction SilentlyContinue
    if ($graphContext) {
        Write-Host "Already connected to Graph as $($graphContext.Account)" -ForegroundColor Gray
        $connected = $true
    }
    else {
        # Connect to Graph
        Connect-MgGraph -Scopes "Chat.Create", "ChatMessage.Send" -ErrorAction Stop
        $connected = $true
    }

    # Send to appropriate chat
    if ($RecipientUpn) {
        # Create new 1:1 chat
        Write-Host "Creating chat with $RecipientUpn..."
        $chat = New-MgChat -ChatType OneOnOne -Members @(
            @{
                "@odata.type" = "#microsoft.graph.aadUserConversationMember"
                "roles" = @("owner")
                "user@odata.bind" = "<https://graph.microsoft.com/v1.0/users('$((Get-MgContext).Account>)')"
            },
            @{
                "@odata.type" = "#microsoft.graph.aadUserConversationMember"
                "roles" = @("owner")
                "user@odata.bind" = "<https://graph.microsoft.com/v1.0/users('$RecipientUpn')>"
            }
        )
        $targetChatId = $chat.Id
    }
    else {
        # Use default chat
        Write-Host "Using default chat: $ChatId"
        $targetChatId = $ChatId
    }

    # Send message
    $message = New-MgChatMessage -ChatId $targetChatId -Body @{
        contentType = "html"
        content = $htmlContent
    }

    # Only disconnect if we initiated the connection
    if (-not $graphContext) {
        Disconnect-MgGraph -ErrorAction SilentlyContinue
    }

    return $message.Id
}

function Show-MigrationStatus {
    param(
        [object]$Data,
        [string]$Location,
        [string]$MigrationStatus
    )

    $statusTitle = switch ($MigrationStatus) {
        "Migrated" { "Migrated" }
        "NotMigrated" { "Not Migrated" }
        "Both" { "All" }
    }

    $locationTitle = if ($Location -eq "All") { "All Locations" } else { $Location }

    Write-Host "`n=== Migration Status - $statusTitle ($locationTitle) ===" -ForegroundColor Cyan

    # Display table
    $Data.Users | Format-Table -AutoSize

    # Summary
    Write-Host "`n📊 Summary:" -ForegroundColor Green
    Write-Host "  Total:    $($Data.TotalCount)"
    Write-Host "  Migrated: $($Data.MigratedCount)"
    Write-Host "  Pending:  $($Data.PendingCount)"
}

# Main execution
Write-Host "Reading migration data from: $CsvPath" -ForegroundColor Gray

# Get data
$migrationData = Get-MigrationData -Path $CsvPath -Location $Location -MigrationStatus $MigrationStatus

# Display locally
Show-MigrationStatus -Data $migrationData -Location $Location -MigrationStatus $MigrationStatus

# Send to Teams if requested
if ($RecipientUpn -or $ChatId -ne "default-chat-id") {
    Write-Host "`nSending to Teams..." -ForegroundColor Yellow

    try {
        $messageId = Send-MigrationStatusToTeams -Data $migrationData -Location $Location -MigrationStatus $MigrationStatus -RecipientUpn $RecipientUpn -ChatId $ChatId
        Write-Host "✓ Message sent to Teams! (ID: $messageId)" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to send to Teams: $_"
    }
}
else {
    Write-Host "`n(No Teams notification - add -RecipientUpn or -ChatId to send)" -ForegroundColor Gray
}

And here is an example for showing Migrated Devices only at Location2.

The full script is also located at Migration Example

There we have it, hope you enjoyed this blog post and learned something from it that will make your life easier. Think of any repetitive tasks, if you want to know when a long script is finished have it notify you chat, or have an agent pick what you and/or your team is having for lunch and put it in the team chat! Would love to hear how you used this blog post or how you incorporate PowerShell and Teams chat/channels.

I created a Github repository for these scripts and for using PowerShell and Teams with other examples and learning as well – Teams GitHub Repo

Have a great day and keep learning and sharing!

Tagged With: 365, Automation, PowerShell, Reporting, Teams

EntraFIDOFinder: New Web UI and Over 70 New Authenticators

January 26, 2026 by ClaytonT Leave a Comment

You read that right, over 70 new authenticators are now approved for Entra Attestation and have been add to the web ui and the PowerShell module! I knew they had to be holding back after these last few updates. Also I’ve updated the web UI and curious of your thoughts. I wanted to make it more modern and easier to view, especially the details window.

Here are a few of the new authenticators, but check the change log for the full list.

Android Authenticator

AAGUID: b93fd961-f2e6-462f-b122-82002247de78

Supported Interfaces:

InterfaceSupported
Biometric✅
USB❌
NFC❌
BLE❌

ATLKey Authenticator

AAGUID: 019614a3-2703-7e35-a453-285fd06c5d24

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC❌
BLE❌

Dapple Authenticator from Dapple Security Inc.

AAGUID: 6dae43be-af9c-417b-8b9f-1b611168ec60

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC❌
BLE❌

Deepnet SafeKey/Classic (FP)

AAGUID: e41b42a3-60ac-4afb-8757-a98f2d7f6c9f

Supported Interfaces:

InterfaceSupported
Biometric✅
USB❌
NFC❌
BLE❌

Deepnet SafeKey/Classic (USB)

AAGUID: b9f6b7b6-f929-4189-bca9-dd951240c132

Supported Interfaces:

InterfaceSupported
Biometric❌
USB❌
NFC❌
BLE❌

ellipticSecure MIRkey USB Authenticator

AAGUID: eb3b131e-59dc-536a-d176-cb7306da10f5

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC❌
BLE❌

Ensurity AUTH BioPro Desktop

AAGUID: 9eb85bb6-9625-4a72-815d-0487830ccab2

Supported Interfaces:

InterfaceSupported
Biometric✅
USB✅
NFC❌
BLE❌

Ensurity AUTH TouchPro

AAGUID: 50cbf15a-238c-4457-8f16-812c43bf3c49

Supported Interfaces:

InterfaceSupported
Biometric❌
USB✅
NFC❌
BLE❌

I’ve been working on better ways to see what keys have been added, removed, or modified, as well as approving valid vendors. It’s not perfected yet, but when I get closer, I’ll do a demo of it.

Let me know what you think of the new design and what functionality you wish it had. Also are there any keys you wish were attestation approved for Entra?

Where to get:
PowerShell Gallery: https://www.powershellgallery.com/packages/EntraFIDOFinder/0.0.22
Github: https://github.com/DevClate/EntraFIDOFinder/tree/main
Web UI: https://devclate.github.io/EntraFIDOFinder/Explorer/

Appreciate you taking the time and stay safe out there!

Tagged With: 365, Automation, Entra, EntraFIDOFinder, PowerShell, Reporting, Security

January 19, 2026 Updates to EntraFIDOFinder

January 19, 2026 by ClaytonT Leave a Comment

It’s been a bit since an update on the PowerShell module EntraFIDOFinder as there haven’t been any new keys and only a few keys have have changed functionality. It is now updated on Github and PowerShell gallery. Are there any features you would like to see on the PowerShell module or the web version? Would love to hear your input!

Updated Keys for V0.0.21

  • Updated ‘USB’ for AAGUID ‘820d89ed-d65a-409e-85cb-f73f0578f82a’ from ‘✅’ to ‘❌’.
  • Updated ‘BLE’ for AAGUID ‘820d89ed-d65a-409e-85cb-f73f0578f82a’ from ‘✅’ to ‘❌’.
  • Updated ‘USB’ for AAGUID ’39a5647e-1853-446c-a1f6-a79bae9f5bc7′ from ‘✅’ to ‘❌’.
  • Updated ‘BLE’ for AAGUID ’39a5647e-1853-446c-a1f6-a79bae9f5bc7′ from ‘✅’ to ‘❌’.
  • Updated ‘USB’ for AAGUID ‘9d3df6ba-282f-11ed-a261-0242ac120002’ from ‘✅’ to ‘❌’.
  • Updated ‘NFC’ for AAGUID ‘9d3df6ba-282f-11ed-a261-0242ac120002’ from ‘❌’ to ‘✅’.
  • Updated ‘USB’ for AAGUID ‘3f59672f-20aa-4afe-b6f4-7e5e916b6d98’ from ‘✅’ to ‘❌’.
  • Updated ‘NFC’ for AAGUID ‘3f59672f-20aa-4afe-b6f4-7e5e916b6d98’ from ‘❌’ to ‘✅’.
  • Updated ‘USB’ for AAGUID ‘d821a7d4-e97c-4cb6-bd82-4237731fd4be’ from ‘❌’ to ‘✅’.
  • Updated ‘USB’ for AAGUID ‘9f77e279-a6e2-4d58-b700-31e5943c6a98’ from ‘❌’ to ‘✅’.

PowerShell Gallery: https://www.powershellgallery.com/packages/EntraFIDOFinder/0.0.21
GitHub: https://github.com/DevClate/EntraFIDOFinder

Tagged With: 365, Automation, EntraFIDOFinder, PowerShell, Security

  • Page 1
  • Page 2
  • Page 3
  • Interim pages omitted …
  • Page 20
  • Go to Next Page »

Primary Sidebar

Clayton Tyger

Tech enthusiast dad who has lost 100lbs and now sometimes has crazy running/biking ideas. Read More…

Find Me On

  • Email
  • GitHub
  • Instagram
  • LinkedIn
  • Twitter

Recent Posts

  • New EntraFIDOFinder Module Version Release!
  • Did you know there is a Giphy rating in Teams? Custom Maester Tests save the day
  • Learning ValidateSet in PowerShell: Valid Values Only
  • Teams Chat and PowerShell – How to add value!
  • EntraFIDOFinder: New Web UI and Over 70 New Authenticators

Categories

  • 365
  • Active Directory
  • AI
  • AzureAD
  • BlueSky
  • Cim
  • Dashboards
  • Documentation
  • Entra
  • Get-WMI
  • Learning
  • Module Monday
  • Nutanix
  • One Liner Wednesday
  • Passwords
  • PDF
  • Planner
  • PowerShell
  • Read-Only Friday
  • Reporting
  • Security
  • Uncategorized
  • Windows
  • WSUS

© 2026 Clatent