Managing Guest Users in Azure Active Directory with PowerShell – A Game Changer for IT Admins!

Are you tired of manually managing guest users in your Azure Active Directory (Azure AD)? Well, have no fear because with the power of PowerShell, we can automate this process and make it a breeze!

The script we’ll be discussing in this post uses the AzureAD PowerShell module to connect to Azure AD and retrieve guest users who have not accepted an invitation within 45 days or who have not logged in within 180 days. And get this – it then automatically removes those users from the organization!

No more manual labor, no more wasted time, and no more headaches – just pure efficiency and organization.

Here’s the script in its entirety, ready to revolutionize your guest user management.

<#
    .NOTES
    ===========================================================================
     Organization:  HDSupply
     Filename:      RB-AZ-RemovePendingGuest.ps1
     ScheduleFrequency: Weekly
    ===========================================================================
    .SYNOPSIS
    This script connects to Azure Active Directory, and then removes all guest users whose state is "PendingAcceptance" and whose refresh tokens were last valid 45 days ago or earlier, and all guest users whose state is "Accepted" and whose refresh tokens were last valid 180 days ago or earlier.
    .DESCRIPTION
    This script uses the AzureAD PowerShell module to connect to Azure AD and retrieve guest users who have not accepted an invitation within 45 days or who have not logged in within 180 days. The script then removes those users from the organization.
    This script is intended to be run on a schedule (e.g. Weekly) to keep the list of guest users in the organization up-to-date. It uses the "CloudAdmin" credentials to connect to Azure AD, which should be configured in the Automation service prior to running the script.
#>

# Variables to get Connected!
[PSCredential]$creds = Get-AutomationPSCredential -Name "ClientSecret"
[PSCredential]$cred = Get-AutomationPSCredential -Name "CloudAdmin"
[string]$clientID = $creds.UserName
[string]$clientSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($creds.Password))
[string]$tenantName = "Enter TenantID or your TenantName"
[hashtable]$reqTokenBody = @{
    Grant_Type    = "client_credentials"
    Scope         = "https://graph.microsoft.com/.default"
    client_Id     = $clientID
    Client_Secret = $clientSecret
}
[string]$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
[string]$TokenResponse | ConvertTo-Json
[string]$Token = $TokenResponse.access_token\

#Import Modules and Connect
Import-Module Microsoft.Graph.Users
Import-module AzureAD
Select-MgProfile -Name "beta"
Connect-MgGraph -AccessToken $Token
Connect-AzureAD -Credential $cred

# This line retrieves all guest users whose state is "PendingAcceptance" and whose refresh tokens were last valid 45 days ago or earlier
[Array]$PendingUsers = Get-AzureADUser -Filter "UserType eq 'Guest' and UserState eq 'PendingAcceptance'" -all $true | where { $_.RefreshTokensValidFromDateTime -lt (Get-Date).AddDays(-45) }
foreach ($PendingUser in $PendingUsers)
{
    # This line assigns the object ID of the current user to a variable
    $PendingUserObjectID = $PendingUser.ObjectId
    # This line outputs a message indicating that the user is being removed
    Write-Output "Removing Pending User $($PendingUser.DisplayName)"
    # This line removes the user using their object ID
    Remove-AzureADUser -ObjectId $PendingUserObjectID
}

#Days
[int]$days = 180

# This line retrieves all guest users whose state is "Accepted"
[Array]$AcceptedUsers = Get-AzureADUser -Filter "UserType eq 'Guest' and UserState eq 'Accepted'" -all $true
Foreach ($acceptedUser in $acceptedUsers)
{
    # This line assigns the object ID of the current user to a variable
    [string]$acceptedUserObjectID = $acceptedUser.ObjectId
    # Get the last sign-in date for the guest
    [Nullable[datetime]]$lastSignInDate = (Get-MgUser -UserId $acceptedUserObjectID -Property 'SigninActivity').signinactivity.LastSignInDateTime
    If ($Null -eq $lastSignInDate -or $lastSignInDate -eq '01/01/0001 00:00:00')
    {
        # Get the Date they Accepted the invitation since they never signed in. 
        $lastSignInDate = $AcceptedUser.RefreshTokensValidFromDateTime
    }
    # Calculate the number of days since the last sign-in
    [int]$daysSinceLastSignIn = (New-TimeSpan -Start $lastSignInDate -End (Get-Date)).Days
    # Check if the guest hasn't signed in for the specified number of days
    if ($daysSinceLastSignIn -ge $days)
    {
        # Output a message indicating that the user is being removed
        Write-Output "Removing user $($acceptedUser.DisplayName) who hasn't signed in for $daysSinceLastSignIn  days"
        # Remove the user
        Remove-AzureADUser -ObjectId $acceptedUserObjectID
    }
    $Null = $lastSignInDate
}

Different ways I have automated Processes using Powershell.

Using AzureAutomate to replace all Scheduled Tasks.

I found that scheduled tasks just suck. You cannot go back and see what happened to the scheduled tasks. When you have to change a password to say a service account you have to change each scheduled task. It also can keep your SQL creds and other creds out of your code out of the box. I’ll post some examples

You can implement Source control with your Azure DevOps. So, when you push changes from your repository, it will add to your runbooks.
Scheduling can be very customized
So, you can store anything. I store SQL info, APPIDs / Secret, this can also store certificates which you can call with some commands I’ll show below.
As you can see here you can assign the Credential to a Variable.
You can drill down in a job and see what happens. Instead of Write-Host you will have to use a Write-Output. This will effectively show you what the script did.
You can use the All Logs tab to see more granular information. It will show you any errors or warnings. There is also an option to run everything in verbose.

There is so many things you can use with AzureAutomate, just explore. There is also a feature to have a run AS account for your hybrid worker / Azure worker. So, you can run all the scripts as a single user. I forget to talk about pricing but for the most part I run close to 4K jobs daily and the cost is around 20 dollars a month.

Using Microsoft Forms / PowerApps + Flow + To Azure Automate.

This allows you to quickly make a form you can hand to the helpdesk team that they can use quickly. You can make this external as well if needed. I do have a form for a vendor we work with so they can add users to a group from their end so they can SSO.

Kurt always breaks things
What the flow looks like on the backend. Basically, takes the information on the form and creates a Job in Azure Automate. If you need to run the command on prem make sure you click advanced under the create job and add the name of your hybrid worker.
What the job looks like on the intake side. Just give it parameters, so when you select it in the flow it will have the parameters already in there, as you can see in the above picture.

Powerapps uses the same concept as above, but you can customize it more than a form. It’s not the prettiest thing in the world since I suck at UI, but it does what you need to do. This PowerApp is mainly given to our helpdesk. I give them access to this instead of direct access to AD / Exchange / O365.

Some quick actions our field technicians have, and you can access this all via Mobile APP which is a cool thing.

Using Universal Dashboard

https://universaldashboard.io This allows you to basically create a website on the frontend but it is all programmed with Powershell which is amazing, and easy to work with. It can use SSO, you can also build API endpoints that you can invoke via rest which is amazing.

This is just an example of a webpage I created for our SOC team. It has a Password never expires, Built-in Admin Accounts, ServiceAccount with Domain Admin Tabs. The following is a modal that allows a helpdesk user build a Admin account, they have to put in the ticket number with the approvals and for the most part it keeps our compliance team happy as all of this info is logged into a SQL database.

There is much more you can do with Universal Dashboard. It is very fun to use but does cost 500 a year for a single website.

Powershell Studios

https://www.sapien.com/software/powershell_studio This is the way I used to build UI Apps with Powershell. It is still relevant, but windows forms / WPF is just old school. I have built 2 apps that our company uses.

The main purpose of this app was to give helpdesk access to perform tasks in a safe manner. So, at this company, we are a hosting provider which means we handle multiple companies. That means that all of the customers sit on the same AD Forest. So, if we were to give helpdesk access to AD / Exchange. They could do something like Get-Mailbox | Set-mailbox which would affect all customers instead of one, that would be a bad day.
We manage a big exchange environment. We also do not have SCCM, but this tool allows you to update Exchange on multiple nodes at the same time, and with exchange you need to properly fail it over before you reboot and turn off bitlocker. This app has been pretty huge to the team, and we use it on a daily basis. Anything that happens in the app writes to a teams channel so everyone in the team is notified if someone is working on a server.

There are soo many ways you can automate with Powershell and I wanted to share my experiences and how these apps have changed my life to make it much easier in the long run. Most of these apps take a very long time to build, but when it is finished, you get soo much relief from the day to day / busy work tasks. If you have any questions on how I used these apps feel free to contact me on LinkedIn.

https://www.linkedin.com/in/alvinerb/