In my last blogpost I shared a script which allows you to automatically revoke a Windows 365 license if it determines that the Cloud PC has not been used for a set amount of days. Today I’ll guide you through the deployment process, use the table of contents below to quickly navigate within this post.
The Azure basics
To run the script on a scheduled basis, we’ll make use of some basic Azure features that you might already use. Feel free to deploy the script at the location of your choosing.
Resource Group
We’ll start by creating a resource group where we will deploy an automation account to.

Automation Account
Next, let’s create the Automation Account, this will host the Runbook that we’ll schedule to trigger the script.
Start by choosing a name and select the resource group you just created.

The rest of the settings can be left default, review and create the Automation Account.
Importing the module
You can access the script as a module or simply copy and paste the code in the Runbook itself. I’ll cover the use of the module, as this requires no changes to the script itself. If you want to make use of a managed identity, skip to managed identity below.
Once the deployment has finished, navigate to the module section. In here we’ll now deploy the module which will allow the future Runbook to load the required Powershell function.
First download the module in zip format from my Github page, and import it into the Automation Account.

Upload the module file (.zip) and make sure to select 7.2 as the runtime version.

Azure will now import the module.

The import process can take up to 5 minutes, once it’s available the required functions can be used inside the Runbook.

Credentials
There are a lot of ways of working with credentials inside Automation Accounts. You can refer to items inside an Azure Key Vault, add the required credentials straight into the Automation Account or use a managed identity. This last one does not require storing any credentials.
If you choose to embed your credentials, follow the steps to create an App Registration. If you go with the Managed Identity, assign all the required access rights to the managed identity instead. In this post I’ll focus on working with the embedded credentials, but you can find a brief summary below on how to work the Managed Identity as well.
Embedded credentials
You can easily work with credentials in Automation Account by storing them in the Automation Account itself. This does not require any other Azure services and allows you to easily deploy a script that you developed without Azure Automation in mind.
Managed identity
Start by copy and pasting the base script inside the Runbook itself and authenticate with “Connect-AzAccount – identity“, this requires the use of a managed identity. If you do this, make sure that you assign all the required access rights, covered in this post, to this managed identity. This has the advantage that you don’t need to store any credentials anywhere. You will need to edit the script a little bit to make this work. Comment the app registration parameters at the beginning of the script and provide the $daysSinceLastConnection and $simulationMode as regular variable. As a last one, change the get-graphToken function to the one listed below:
function get-graphToken() {
$token = (Get-AzAccessToken -ResourceUrl https://graph.microsoft.com).Token
Return "Bearer " + ($token).ToString()
}
Create the App Registration
The Automation Account will only host the Runbook which will trigger a script. To allow the Runbook to access your tenant, you have to provide an authentication mechanism. For this we’ll create a new app registration in Entra ID.
Navigate to Entra ID and open up the App registrations page and start a new registration.

Provide a name and select that the app is only to be used inside your tenant.

The next page will provide you with half of the required information that you need later on.
Fetch the Application ID, and take a note of your Tenant ID as well.

Now navigate to certificates and secrets, this secret will act as the password which the App Registration can use to connect to your tenant.

Give it a friendly name and determine the expiration period.

Take a note of the Value of the secret, you will need this as well.

Grant Access Rights
Now that you have the App Registration (or the Managed Identity), you need to assign it the minimum amount of permissions that are required for the script to successfully run.
API Permissions
Open up the API permissions page, and add a new permission.

Select Microsoft Graph

As the script won’t need any user interaction it will only require Application permissions.

The script requires the following permissions, add them one by one:
- DeviceManagementManagedDevices.Read.All,
- GroupMember.ReadWrite.All
- CloudPC.Read.All
You should now end up with the following view:

As a last step press the Grant Admin consent button

The final result will be like this:

Entra ID Permissions
Navigate back to Entra ID and open up “Roles and administrators”. From here select the “Directory Readers” and click on “Add assignments”.

Start typing the name of the App registration you made earlier and add it.

Your App registration now has all the required permissions to run the script.
Runbook
Now we will prepare the content of the Automation Account.
Add the credentials
To embed the credentials navigate to the Automation Account and select the Credentials property. In here add the information you gathered earlier:
- Tenant ID:
- Username: Tenant Name
- Password: Tenant ID
- W365 – App registration:
- Username: App ID
- Password: Secret Value

Create the Runbook
The last step is creating the Runbook. Navigate to the Runbook section within the Automation Account and Create a Runbook.

Choose a name and select Powershell 7.2 as the runtime version, optionally provide a description.

Edit the Runbook:

Copy paste the code below inside the editor, change the name of credentials in the first two lines if you used different names.
#Fetch credentials
$credAppRegistration = Get-AutomationPSCredential -Name "W365 - App registration"
$credTenantID = Get-AutomationPSCredential -Name "Tenant ID"
#Resolve credentials
$appId = $($credAppRegistration.username)
$appSecret = $([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credAppRegistration.Password)))
$tenantID = $([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credTenantID.Password)))
revoke-Windows365UnusedLicense -app_id "$($appId)" -app_secret "$($appSecret)" -tenantId "$($tenantID)"
Save the script, you can always verify first by using the Test pane.

When you test the script, you see which actions the script will take.

After testing, don’t forget to publish the Runbook.

After you ran this for a couple of days in your production environment you can change the “-simulationMode” parameter to $False for the script to effectively remove the user from the license group.
revoke-Windows365UnusedLicense -app_id "$($appId)" -app_secret "$($appSecret)" -tenantId "$($tenantID)" -simulationMode $false
If you want to change the amount of days before the license is revoked for the user, add the “-daysSinceLastConnection” parameter with an amount of days specified. In the example below it will only revoke access to a Cloud PC if it hasn’t been used for 60 days.
revoke-Windows365UnusedLicense -app_id "$($appId)" -app_secret "$($appSecret)" -tenantId "$($tenantID)" -simulationMode $false -daysSinceLastConnection 60
Trigger on a Schedule
The beauty of a Runbook is that they are very easy to trigger, if you want you can even trigger them based on a webhook. For this use case, a regular schedule that triggers once a week is sufficient.
Navigate back to the Automation Account and open the Runbook. Within the Runbook you have the option to create a new schedule.

Select that you want to assign a schedule.

As a last step simply select an existing schedule or create a new one.

Set the frequency at your desired interval, and the Runbook will be triggered at that exact time.

Don’t forget to press OK, one last time.

That’s it, the script will now run on a schedule and will revoke any licenses to Cloud PCs if the Cloud PC has not been used for a predetermined time period.
Good to know
- It’s currently not possible to select if you want this script to run on only Enterprise or Frontline Cloud PCs, let me know if this would be something you would be interested in.
- Only Cloud PCs provisioned through group assignments are covered. Direct assignments will not be revoked.
- Currently the lifetime of the Token is not verified. This means that the script might stop working after it hits your token invalidation period. By default the token lifetime is set to 60 minutes.
- An Azure Automation account is free to use for the first 500 minutes/month.
- Do you want extra functionality? Let me know.




Leave a Reply