Azure – Using KeyVault with PowerShell – Updated

Azure Key Vault is used to safely manage Secrets, Certificates and Crytographic Keys used by cloud and on-premise applications. After the secrets are stored in Azure Key Vault, we can assign access policies to user accounts or SPNs to retrieve and use them.

In this post, I will cover,

  • How to create Azure Key Vault
    • Create and update secrets in Azure Key Vault
  • Create a new Azure AD application and SPN
    • Create a client Secret
  • Assign a policy in Azure Key Vault to allow access to the SPN we create
  • Store the Azure AD application ID and client secret in the SecretStore vault
  • Retrieve Secret from Azure Key vault using credentials stored in SecretStore vault

I’ll go through the steps in both the portal and via PowerShell.

Before proceeding further, make sure you are connected to Azure AD and Azure via PowerShell. You may or may not use the same global admin account to connect to both Azure AD and Azure, either way you can use the below cmdlets and adjust it accordingly where necessary.

$AzureADcred = Get-Credential
Connect-AzureAD -credential $AzureADcred
$Azcred = Get-Credential
$SubsName = Read-Host "Enter Azure Subscription Name"
Connect-AzAccount -Credential $Azcred -Subscription $SubsName

Azure Key Vault

Register Resource Provider

Using PowerShell

Use below cmdlet to register ‘Microsoft.KeyVault‘ as a resource provider in the subscription,

Register-AzResourceProvider -ProviderNamespace "Microsoft.KeyVault"

To confirm the registration is successful,

Get-AzResourceProvider | Where-Object {$_.ProviderNamespace -contains "Microsoft.KeyVault"} | select ProviderNamespace, RegistrationState

Using Azure Portal

  1. Login to Azure Portal (https://portal.azure.com/)
  2. Navigate to Subscriptions
  3. Click to select the desired Subscription
  4. In the left navigation menu, click on Resource providers
  5. Search for Microsoft.KeyVault
  6. Click on Microsoft.KeyVault
  7. Click Register
  8. Once complete, Status column for Microsoft.KeyVault will show Registered
Register Resource provider – Microsoft.KeyVault

Create Azure Key Vault

Using PowerShell

To proceed further, launch PowerShell as admin and install the Az.KeyVault PowerShell Module

Install-Module -Name Az.KeyVault

The New-AzKeyVault can be used to create a new Key Vault in Azure. To determine the locations where Key Vault is offered, use the below cmdlet,

Get-AzLocation | Where-Object {$_.Providers -contains "Microsoft.KeyVault"} | ft

Use the below cmdlets to create a new Key Vault,

$kvName = Read-Host "Enter a name for Key Vault"
$rg = Read-Host "Enter Resource Group Name"
$loc = Read-Host "Enter Azure location"
New-AzKeyVault -VaultName $kvName -ResourceGroupName $rg -Location $loc

To confirm Key Vault creation,

Get-AzKeyVault

Using Azure Portal

To cerate a new Key Vault from the Azure portal,

  1. Login to Azure Portal (https://portal.azure.com/)
  2. Search for key vault
  3. Click Create or Create key vault
  4. Provide below information,
    • Subscription
    • Resource group
    • Key vault name
    • Region
    • Pricing tier
  5. Leave the other options default and click Review + create
    • The other options in the creation steps,
      • Access policy = I’ll go through it later in this post
      • Networking = All networks to make it publicly accessible
      • Tags = As necessary

Below are my settings,

Create a key vault

Create SPN in Azure AD

In this step, we’re creating a service principal in Azure AD. We will assign permissions for this SP to retrieve secrets from the Azure Key vault in later step.

In Azure AD, the application registration is the template used to create the SP. Also, the SP is what can be authenticated and authorized. Application and SP are associated by Application ID and they differ in it Object ID.

To create a new Azure AD application,

$appname = Read-Host "Enter a name for the Azure AD application"
New-AzureADApplication -DisplayName $appname

To create a service principal,

$appname = Read-Host "Enter name of Azure AD application"
$AppId = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").AppId
New-AzureADServicePrincipal -AccountEnabled $true -AppId $AppId -DisplayName $appname

Create Client Secret

Next, we create a new client secret using the Get-AzureADApplicationPasswordCredential cmdlet,

$appname = Read-Host "Enter Azure AD application name to determine Object ID"
$appObjID = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").Objectid
$KeyId = Read-Host "Enter value for secret identifier"
New-AzureADApplicationPasswordCredential -ObjectId $appObjID -CustomKeyIdentifier $KeyId

Copy the value in the output to a notepad as I have highlighted above. This value will not be available to copy later.

Assign Permissions

Using PowerShell

We can assign necessary permissions to the Azure AD application we created in above step, using the Set-AzKeyVaultAccessPolicy cmdlet,

$appname = Read-Host "Enter Azure AD application name to determine Object ID"
$Appid = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").AppId
$kvName = Read-Host "Enter a name for Key Vault"
Set-AzKeyVaultAccessPolicy -VaultName $kvName -ServicePrincipalName $Appid -PermissionsToSecrets list,get

Using Azure Portal

  1. Login to Azure Portal (https://portal.azure.com/)
  2. Search for Key vault
  3. Click on the Key Vault we created earlier
  4. In the left navigation menu, click on Access policies
  5. Select Permission model as Vault access policy
  6. Click +Add Access Policy
  7. In the Add access policy window
    • For Secret permissions, select Get and List
    • For Select principal, select the SPN we created earlier
  8. Click Add
  9. Click Save to save policy

Manage Secrets in Key Vault

Applications, scripts or users can create, update, delete and retrieve secrets if they have the necessary policy assigned to them

Creating/Updating Secrets

To create a new secret, we can use the Set-AzureKeyVaultSecret cmdlet,

$Secret = ConvertTo-SecureString -String 'Password' -AsPlainText -Force
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
Set-AzKeyVaultSecret -VaultName $kvName -Name $SecName -SecretValue $Secret

The secret can be updated to a new value using the same Set-AzureKeyVaultSecret cmdlet,

$Secret = ConvertTo-SecureString -String 'Password' -AsPlainText -Force
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
$Expires = (Get-Date).AddYears(2).ToUniversalTime()
$NBF =(Get-Date).ToUniversalTime()
$Tags = @{ 'Department' = 'IT'; 'Environment' = 'Production'}
Set-AzKeyVaultSecret -VaultName $kvName -Name $SecName -SecretValue $Secret -Expires $Expires -NotBefore $NBF -Tags $Tags

Retrieving Secrets

To retrieve the current version of a secret, we use the Get-AzureKeyVaultSecret cmdlet,

$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
$secruretext = (Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName).SecretValue

This will assign the stored secret to the $secruretext variable as a SecureString. We can now pass this to any other cmdlets that require a SecureString.

As I’ve already covered the Microsoft.PowerShell.SecretManagement and Microsoft.PowerShell.SecretStore PS modules in an earlier post, I’ll follow on the premise and store the client secret we created in the local vault. This way, I don’t have to save the client secret in the code as plaintext. To do this, we can store the Application ID and Client secret in a PSCredential object to the store,

$credential = Get-Credential
Set-Secret -Name azkv-01 -Secret $credential

In the Windows PowerShell Credential request window, for User Name input the Application (client) ID of the Azure AD application and for password input the Client Secret value we copied into a notepad earlier.

I’ve also created another secret as string in the local vault with my tenant ID value.

Putting this all together, we can use these below lines in PowerShell automation scripts,

$vpwd = (Import-CliXml "C:\Scripts\vpd.xml").Password
Unlock-SecretStore -Password $vpwd
$TenantId = Get-Secret -Vault CredsDB -Name TenantId -AsPlainText
$credential = Get-Secret -Vault CredsDB -Name azkv-01
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $TenantId

To retrieve the secure string stored in the Azure Key vault, I’m using these lines below. Also for demo purposes, I’m including the -AsPlainText to the Get-AzKeyVaultSecret cmdlet but as I mentioned earlier, we can store this secure string to a variable and pass it on to other cmdlets.

$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName -AsPlainText
#or
$secruretext = (Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName).SecretValue

I know this was a lengthy post and it may have gotten a little confusing right at the end with too many things named vault🤷‍♂️

Hope this helped you out in figuring out in including Azure Key Vault in your PowerShell automations.

Thank you for stopping by ✌

PowerShell – Securely Managing Credentials – Updated

When it comes to automation with PowerShell we come across scenarios where credentials are needed for the script to run and I’ve seen scripts being used by admins where the passwords are there in plain-text. It starts with..’this is only for testing‘,’oh!..that’s just a service account‘ and next thing you know, it is in production and the said service account has more previleged roles attached to it than when it was in testing.

I’ve done it too but I’ve realized that I can spend some time to understand and make it a practice on storing the passwords securely. I went over this issue briefly in a different post when earlier. In this post, I will go over the steps on how to use the Microsoft’s SecretManagement and Secret Store modules to manage passwords securely in a script and interactively.

And yes, there is a long list of 3rd party secret vaults that can be used to accomplish this like HashiCorp Vault, LastPass, KeePass, etc. I’ve started using Azure Key Vault which is great and doesn’t cost a lot. The Azure Key Vault is great for storing and sharing secrets in an organization and also to set a process around it. I will cover this in a future post.

Installing the modules

To start storing and managing passwords from a encrypted vault, we need to install the PowerShell SecretManagement and SecretStore modules. These modules require Windows PowerShell version 5.1 or PowerShell Core 6.x, 7.x

Microsoft.PowerShell.SecretManagement – Provides a convenient way to store and retrieve secrets
Microsoft.PowerShell.SecretStore – Provides local secure store extension vault for Microsoft.PowerShell.SecretManagement module

To install the modules,

  1. Open PowerShell as admin
  2. Set PowerShell’s execution policy to RemoteSigned
Set-ExecutionPolicy RemoteSigned
  1. Run the following command
    • On confirmation prompt, press A to continue
Install-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
  1. To confirm successful installation,
Get-Module -ListAvailable Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore

and to display all the available cmdlets in both modules,

Get-Command -Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore | Sort-Object Source

Create a secret store vault

First, we need to create a local secret vault. I will name mine CredsDB

$vaultName = Read-Host "Enter a name for the vault"
$vaultDesc =  Read-Host "Enter vault description"
Register-SecretVault -Name $vaultName -ModuleName Microsoft.PowerShell.SecretStore -Description $vaultDesc

To display the registered vault,

Get-SecretVault

To set a master password to the SecretStore vault

To create a master password to access the SecretStore,

Get-SecretStoreConfiguration

The following settings determine access to the password stores,

  • Scope – The Scope is always CurrentUser. AllUsers Scope is not supported
  • Authentication – Access vault using a master password
  • PasswordTimeout – 900 seconds, duration of the session before we need to enter the master password
  • Interaction – Prompt – to make changes

Note: If you forget the vault master password, you won’t be able to access stored data.

To change master password,

$oldPassword = Read-Host "Enter old password" -AsSecureString
$newPassword = Read-Host "Enter new password" -AsSecureString
Set-SecretStorePassword -NewPassword $newPassword -Password $oldPassword

Storing and updating secrets

Now that we have created a new secret vault, we are ready to start storing sensitive information into it. The secret store accepts the following data types as secrets,

  • Byte[]
  • Hashtable
  • PSCredential
  • SecureString
  • String

To add a new username and password, PSCredential object to the store,

$credential = Get-Credential
Set-Secret -Name Cred1 -Secret $credential

If you missed to set the master password in the earlier step I described, you’ll be prompted to set the master password while running the Set-Secret cmdlet for the first time.

If you need to update the secret at a later point in time, use the same Set-Secret cmdlet to overwrite the existing secret

Retrieving secrets

To retrieve the entries in the vault, we can use the below cmdlet to unlock the vault first. Type the vault’s password and press enter,

Unlock-SecretStore

To display entries in the secret vault,

Get-SecretInfo

To retrieve a secret’s value shown as System.Security.SecureString,

$Secret = Read-Host "Enter name of the secret"
Get-Secret -Name $Secret

To view the password in plaintext,

$Secret = Read-Host "Enter name of the secret"
(Get-Secret -Name $Secret).GetNetworkCredential() | Select UserName, Password

Using secrets in PowerShell automation

We’ve registered, created a new vault and also created a new secret and also retrieved the stored secret in the above steps. The above retrieval process needs manual interaction and in automation we need to avoid that. I know what you are thinking, if there is a master password, we’ll need to type that in anyway to unlock the stored secret.

You might be tempted to disable the request of the master password request to access the secret by using the below cmdlet, but please don’t. This method might be good to do some quick testing but is not recommended in production environments.

Set-SecretStoreConfiguration -Authentication None

There are many options to unlocking the secret store without manually entering the master password while making sure it is not stored anywhere in plain text. One method is to save the master password in an encrypted xml file.

Use the below command to save master password in a CliXml file.

  • The command will prompt for credentials
    • Type any username
    • Type the master password
  • You can name the xml file to your preference
  • It is recommended to store the xml file in a location where you can lock down the permissions
Get-Credential | Export-CliXml c:\scripts\vpd.xml

We can confirm the credential file exists and contains the encrypted master password. The XML file will not display the password in plain text as you can see in the screenshot.

Get-Content c:\scripts\vpd.xml

Now we can import the encrypted password from the xml file to a variable

$vpwd = (Import-CliXml c:\scripts\vpd.xml).Password

We can use this above variable to unlock the Secret Store,

Unlock-SecretStore -Password $vpwd

To retrieve the secret,

$Secret = Read-Host "Enter name of the secret"
(Get-Secret -Name $Secret).GetNetworkCredential() | Select Username,Password

With this in place, to keep things simple you can use the variable and Unlock-SecretStore in PowerShell automation scripts. We can use this to connect to Azure AD tenant or to O365.

Here is how I do it,

$vpwd = (Import-CliXml c:\scripts\vpd.xml).Password
Unlock-SecretStore -Password $vpwd
$credential = Get-Secret -Vault CredsDB -Name Cred1
Connect-AzureAD -Credential $credential
Connect-ExchangeOnline -credential $credential

Hope this post helped you in understanding how to store credentials securely with the SecretManagement and Secret Store modules.

Thank you for stopping by.✌

Teams – Enable/Apply Sensitivity Labels

In a O365 tenant I manage, I had rolled out the Azure Information Protection labels from earlier. The recent requirement was to make sure the sensitivity labels will apply to group across services like Outlook, Microsoft Teams and SharePoint online.

When I checked the sensitivity label, I noticed the ‘Groups and sites’ option greyed out and which lead me to research a bit into this and write my findings below,

Groups & sites greyed out

Enable sensitivity labels for containers in Azure AD

Sensitivity labeling for containers i.e., groups and sites, should enabled before we can configure the settings in the sensitivity labeling wizard. Else, it will be greyed out as in screenshot above.

To determine current group settings for your Azure AD organization, use the below cmdlet. If no group settings are defined, this cmdlet won’t return any output value.

Get-AzureADDirectorySetting | fl

In my scenario, I have only one setting and it was easier to see it. But your organization might have more than one setting and in that case, you can use this below cmdlet to search and determine the setting.

Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id
EnableMIPLabels = false

Below, I’m storing the value of the cmdlet’s output into the $Setting variable. And once stored, I’m setting ‘True’ as the value for ‘EnableMIPLabels’. I’m listing out both methods, what I used and what you can potentially use. The second method is much easier.

$Setting = Get-AzureADDirectorySetting -Id <Group.Unified policy's Id from your tenant>
$Setting["EnableMIPLabels"] = "True"
Set-AzureADDirectorySetting -Id $Setting.Id -DirectorySetting $Setting

or you can use this,

$Setting = Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id
$Setting["EnableMIPLabels"] = "True"
Set-AzureADDirectorySetting -Id $Setting.Id -DirectorySetting $Setting
EnableMIPLabels = true

Synchronize sensitivity labels to Azure AD

  1. Connect to Security & Compliance PowerShell using the Exchange Online PowerShell V2 module
  2. Run Connect-IPPSSession -UserPrincipalName username@tenantdomain.com
  3. Run the following cmdlet to use sensitivity labels in M365 groups,

Note: This is a one-time procedure.

Execute-AzureAdLabelSync

Once enabled, you can configure protection settings for “Groups & sites” and “Files & emails” within a single sensitivity label.

Groups & sites not greyed out

Thank you for stopping by. ✌

Azure AD – Password Hash Synchronization – Non-Expiring Password Service Accounts

Recently I worked on implementing password hash synchronization with Azure AD Connect sync in one of the tenants I manage. This interested me on so many levels but especially the lengths that Microsoft has gone to protect this hash sync process fascinated me.

To synchronize a password, Azure AD Connect sync extracts password’s hash from on-premises AD. Extra security processing (meaning, When a user attempts to sign in to Azure AD and enters their password, the password is run through MD4+salt+PBKDF2+HMAC-SHA256 process) is applied to the password hash before it is synchronized to the Azure AD authentication service. Passwords are synchronized on a per-user basis and in chronological order.

When password hash synchronization is enabled, by default the cloud account password is set to ‘Never Expire’. This is a bit scary because if left in default state, users can still login to applications with their password that is expired in on-premise AD. Also meaning that the on-premise AD password expiration policy is not in sync with Azure AD. Users can be forced to comply with your Azure AD password expiration policy by enabling the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature.

When EnforceCloudPasswordPolicyForPasswordSyncedUsers is disabled (which is the default setting), Azure AD Connect sets the PasswordPolicies attribute of synchronized users to “DisablePasswordExpiration”

Get-MsolDirSyncFeatures

To enable the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature, run the following command using the MSOnline PS module

Set-MsolDirSyncFeature -Feature EnforceCloudPasswordPolicyForPasswordSyncedUsers -Enable $True

Once enabled, Azure AD does not go to each synchronized user to remove the “DisablePasswordExpiration” value from the ’PasswordPolicies’ attribute. But waits till the user’s next password change to the “DisablePasswordExpiration” from the ‘PasswordPolicies’ which is when the next password sync happens.

For this reason it is recommended to enable EnforceCloudPasswordPolicyForPasswordSyncedUsers prior to enabling password hash sync, this way the initial sync of password hashes does not add the “DisablePasswordExpiration” value to the ‘PasswordPolicies’ attribute for the users. But if you miss enabling this it is not the end of the world.

Use the below cmdlet to determine a user’s Azure AD password policy,

$user = Read-host "Enter user's UPN:"
Get-AzureADUser -objectID $user | Select DisplayName, passwordpolicies

The issue we need to address are the service accounts that live in on-premise AD with non-expiring password and their identity is synced to Azure AD so these accounts can be used in various applications. So, if you enable EnforceCloudPasswordPolicyForPasswordSyncedUsers feature and then enable password hash sync, your service accounts with non-expiring password will not have any password policy attached to it in Azure AD. These accounts will need the “DisablePasswordExpiration” policy set to them explicitly.

You can set this policy for all the non-expiring password account using the below script,

$ou1 = Get-ADUser -SearchBase 'OU=Users,OU=OU1,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName
$ou2 = Get-ADUser -SearchBase 'OU=Users,OU=OU2,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName #if you are syncing only certain OUs, this helps

$AllOu = $ou1 + $ou2

foreach ($account in $AllOU) {

            $t = (Get-AzureADUser -ObjectID $account.userPrincipalName).passwordpolicies
            if ($t -ne "DisablePasswordExpiration") {
               Set-AzureADUser -ObjectID $account.userPrincipalName -PasswordPolicies "DisablePasswordExpiration"                
    }
}

The Azure AD password policy for these account is empty when it is created in on-premise AD and the administrator creating the account can set the “DisablePasswordExpiration” policy on a per-account basis by running this below,

$user = Read-host "Enter the user's UPN"
Set-AzureADUser -ObjectID $user -PasswordPolicies "DisablePasswordExpiration"

Another caveat here is, when these account’s password is changed on-premises for whatever reason the ‘PasswordPolicies’ value switched to ‘None’

This can happen when,

  • You allow helpdesk resets service account passwords
  • You allow service account owners reset account password
  • Application admins who use these service accounts quit or change job positions and the password needs to be changed
  • Administrator creating the service account on-premise forgot to set the password policy by running the Set-AzureADUser

When the ‘PasswordPolicies’ value gets set to ‘None’ as I mentioned earlier, the account sign-ins to Azure AD will fail with error code ‘50055 — InvalidPasswordExpiredPassword — The password is expired’.

To avoid this, you can create a scheduled task on an on-premise server which run the PS script from above maybe once a week. An issue here will be for these script to run the Connect-AzureAD cmdlet needs to be run. There are probably a thousand different ways to accomplish this but for the sake of simplicity, you can consider these two options,

  • Store your credentials in plan text in the script
  • Create an encrypted, secure string password file and use it in the script

Store credentials in plain text

This is not recommended practice and never be used but there might be scenarios where you may have to use it for some quick tests. In such a scenario, you can just do something like this,

$user = "adminaccount@domain.com"
$pwd = "MySeCur3P@$$w0rd"
$secpwd = ConvertTo-SecureString $pwd -AsPlainText -Force 
$cred = New-Object System.Management.Automation.PSCredential ($user, $secpwd)
Connect-AzureAD -Credential $cred | Out-Null

All this being said, don’t use this method and if you do, please remember to delete the script after testing.

Use secure string password file

This method is far better to securely store password for automation scripts. The idea is, you create password file which has the password stored encrypted. It goes without saying that it is not a good idea to save this file as password.txt.

To create password file,

(Get-Credential).Password | ConvertFrom-SecureString | Out-File "C:\temp\sec.txt"

To silently connect to Azure AD using stored credentials,

$User = "adminaccount@domain.com"
$File = "C:\temp\sec.txt"
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $File | ConvertTo-SecureString)
Connect-AzureAD -credential $credential | Out-Null

Keep in mind that you can only use this file on the computer or server where you created it. This happens because of how Windows Data Protection API encrypts the information from current computer or user account. If you try the file on a different computer you’ll get a ‘Key not valid..’ error. I think this is great and adds another layer of security.

Also, this won’t the password being decrypted or from reusing the encrypted password if it falls into wrong hands. The basic idea here is not to store password in plaintext. This method is not foolproof but good enough.

If you need a secure password file that needs to be used in multiple scripts and on different machines, AES encryption algorithm can be used and covering that will take this post way off the Azure AD non-expiring password accounts topic..too late for that..I know. 😁

Hope this helped you setup your environment before those password expired in Azure AD.

Thank you for stopping by..✌

Azure AD – Manage stale devices

A device that has been registered with Azure AD but has not been used to access any cloud apps for a specific timeframe is stale device. In a perfect world, Azure AD registered devices should be unregistered when they aren’t needed anymore..well..duh!

In the environments I manage, most of the times devices are lost, broken, forgotten in trains and taxis or have their OS reinstalled. These numbers grow fairly quickly if a process is not put in place. I had to live and learn this.

Beyond interfering with the device’s general lifecycle, these stale devices can make it hard for identifying the devices associated with the user. Plus it’s ideal to have a clean state of devices to meet various compliance requirements.

Define a policy

Similar to having policies for on-premise AD objects, it is better to define a policy of Azure AD objects.

  • Define a timeframe – It is better to pick a timeframe that follows your on-premise AD inactive objects
  • Categorize to better understand your stale device management
    • MDM-controlled devices – Retire devices in Intune or other MDM solutions before disabling or deleting it
    • System-managed devices – Don’t delete. These devices are generally devices such as Autopilot. Once deleted, these devices can’t be re-provisioned
    • Hybrid Azure AD joined devices
      • Windows 10 – Disable or delete in on-premises AD, and let Azure AD Connect synchronize the changed device status to Azure AD
      • Windows 7/8 – Disable or delete in on-premises AD, Azure AD Connect can’t be used disable or delete these devices in Azure AD. Instead, these devices must be disabled/deleted in Azure AD.
    • Azure AD joined devices – Disable or delete in Azure AD
    • Azure AD registered devices – Disable or delete in Azure AD

What happens when a device is disabled?

Any authentication where a device is being used to authenticate to Azure AD are denied.

Hybrid Azure AD joined device – Users might be able to use the device to sign-in to their on-premises domain. However, they can’t access Azure AD resources such as Microsoft 365
Azure AD joined device – Users can’t use the device to sign in
Mobile devices – Users can’t access Azure AD resources such as Microsoft 365

How to remove a registration on the client?

Even after a device is disabled or deleted in the Azure portal or by using Windows PowerShell, the local state on the device will say that it’s still registered.

This operation is by design. In this case, the device doesn’t have access to resources in the cloud. Deleting an Azure AD device does not remove registration on the client. It will only prevent access to resources using device as an identity.

To remove Windows 10 device registration – Go to Settings > Accounts > Access Work or School. Select your account and select Disconnect. Device registration is per user profile

For iOS and Android, Open Microsoft Authenticator, Settings > Device Registration and select Unregister device

Detecting stale devices

The ApproximateLastLogonTimestamp or activity timestamp property in Azure AD comes in handy to detect stale devices. If the difference between now and the value of the activity timestamp exceeds the defined timeframe for active devices, a device is considered to be stale. The evaluation of the activity timestamp is triggered by an authentication attempt of a device.

Cleanup stale devices

The Azure AD portal does allow you to remove stale devices but it is better to use PowerShell. Typical steps are as follows,

  1. Connect to Azure AD using Connect-AzureAD cmdlet
  2. Get list of devices using Get-AzureADDevice (Get-AzureADDevice cmdlet excludes system-managed devices by default)
  3. Disable device using Set-AzureADDevice cmdlet (disable by using -AccountEnabled option)
  4. Define and wait for grace period depending on your environment before deleting devices
  5. Remove device using Remove-AzureADDevice cmdlet

The account updating devices in Azure AD will need one of the following roles assigned:

  • Global Administrator
  • Cloud Device Administrator
  • Intune Service Administrator

To get all devices and store the returned data in a CSV file:

Get-AzureADDevice -All:$true | select-object -Property AccountEnabled, DeviceId, DeviceOSType, DeviceOSVersion, DisplayName, DeviceTrustType, ApproximateLastLogonTimestamp | export-csv stale-devicelist.csv -NoTypeInformation

To get all devices that haven’t logged on in 120 days and return data in a CSV file:

$sd = (Get-Date).AddDays(-120)
Get-AzureADDevice -All:$true | Where {$_.ApproximateLastLogonTimeStamp -le $sd} | select-object -Property AccountEnabled, DeviceId, DeviceOSType, DeviceOSVersion, DisplayName, DeviceTrustType, ApproximateLastLogonTimestamp | export-csv devicelist-olderthan-120days.csv -NoTypeInformation

Disable devices that haven’t logged on in the past 120 days:

$sd = (Get-Date).AddDays(-120)
Get-AzureADDevice -All:$true | Where {$_.ApproximateLastLogonTimeStamp -le $sd}
foreach ($Device in $Devices) {
Set-AzureADDevice -ObjectId $Device.ObjectId -AccountEnabled $false
}

Delete disabled devices that have been inactive the past 120 days. Remove-AzureADDevice will delete devices without prompting. There is no way to recover deleted devices.

$sd = (Get-Date).AddDays(-120)
$Devices = Get-AzureADDevice -All:$true | Where {($_.ApproximateLastLogonTimeStamp -le $sd) -and ($_.AccountEnabled -eq $false)}
foreach ($Device in $Devices) {
Remove-AzureADDevice -ObjectId $Device.ObjectId
}

Remember that when configured, BitLocker keys for Windows 10 devices are stored on the device object in Azure AD. If you delete a stale device, you also delete the BitLocker keys that are stored on the device. Confirm that your cleanup policy aligns with the actual lifecycle of your device before deleting a stale device.

Thank you for stopping by.✌