Using Azure Automation to Invite Entra ID B2B Users

In a previous article about using Entra ID B2B users, I needed to set up some user extensions. In that article, I also mentioned that many customers needed to access the environment, which meant that multiple users needed to be changed.
As this is an ongoing business and we like to automate these kinds of tasks, I looked for an automation option. Creating a script wasn’t the problem, but using it to connect to Entra ID by entering credentials every time or putting them into the script was the hard part of the search.

After some time, I stumbled upon Azure Automation, where you can create runbooks (scripts) and add them to a scheduled task.

What’s Azure automation

Azure Automation is a cloud-based service provided by Microsoft Azure that enables users to automate various manual, time-consuming, and repetitive tasks across Azure and on-premises environments. It offers tools for process automation, configuration management, and update management. Using PowerShell or Python to automate complex tasks, users can create, schedule, and manage runbooks (workflow scripts). Azure Automation also provides Desired State Configuration (DSC) to define and enforce system configurations. Additionally, it integrates with other Azure services, allowing users to orchestrate end-to-end workflows for efficient and consistent management of resources in the Azure cloud.

Creating an Azure Automation account

We must create an Azure Automation account before using Azure Automation and deploying our script. Sign in to the Azure portal and search for Azure Automation. Click on “Create” and follow the wizard, here, you will see the output of “Review + Create.”

Powershell Modules

When using Azure Automation, verifying the availability of the intended PowerShell modules is crucial. For this situation, the following modules are essential:

  • Microsoft.Graph.Authentication:
    Responsible for establishing connections (Connect-MgGraph) and severing connections (Disconnect-MgGraph) to MgGraph.
  • Microsoft.Graph.Groups:
    Required for retrieving group members (Get-MgGroupMember) and obtaining information based on a group ID (Get-MgGroup).
  • Microsoft.Graph.Identity.SignIns:
    Necessary for initiating invitations (New-MgInvitation) through identity sign-ins.
  • Microsoft.Graph.Users:
    Utilized for fetching user information (Get-MgUser) and facilitating user updates (Update-MgUser).

To add the modules to the Azure Automation account, open the account and go to “Modules,” then follow the below steps to add the module. Repeat these steps until all required modules are added:

  1. Click on “Add a Module”
  2. Select “Browse from gallery”
  3. Click on “Click here to browse from gallery”
    • Search the module and select
    • click on “Select” at the bottom of the page.
  4. Now set the “Runtime Version”. In our case, it’s 5.1.
  5. Click “Import”

To verify if all of the required Powershell modules are available or still being imported, click on “Modules” within the Azure Automation account.

After adding the appropriate Powershell modules, we need to add the required permissions.

Permissions

To get our script working with the appropriate permissions, we need the following permissions:

  • User.ReadWrite.All
  • User.Invite.All
  • Directory.Read.All

As we use a Managed Identity, we need to add the permissions using Graph Explorer. First, we must connect to the Graph Explorer: https://aka.ms/ge and sign in using an account in the correct tenant. You can’t switch tenant/directory when using Graph Explorer.

Step 1: Finding the Service Principal

We first need to find the service principal for our managed identity (automation account). To do so, we need to perform the following query, where displayname equals our Managed Identity name, in our case, “CTX-DaaS”:

HTML
GET https://graph.microsoft.com/v1.0/servicePrincipals?$search="displayName:CTX-Daas"

Write down the ID. We need this in step 4.

Step 2: Finding the Microsoft Graph Service principal ID

As the Microsoft Graph API always has the following AppId “00000003-0000-0000-c000-000000000000”, we can create a filter and only look for this AppId. We can use the following query to get the ID:

HTML
GET https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appId eq '00000003-0000-0000-c000-000000000000'

Write down the ID. We need this in steps 3 and 4.

Step 3: Finding the Application Roles (Permissions) id

Now, we need to get the App Role IDs, as these are always the same and well-documented by Microsoft. We can search for them on the following site: https://learn.microsoft.com/en-us/graph/permissions-reference.

  • User.ReadWrite.All: “741f803b-c850-494e-b5df-cde7c675a1ca”
  • User.Invite.All: “09850681-111b-4a89-9bed-3f2cae46d706”
  • Directory.Read.All”: “7ab1d382-f21e-4acd-a863-ba3e13f7da61”

When you would like to use Graph Explorer, this is also possible, and you follow the below step: As we can’t add a filter to the query, we need to use the old-fashioned way and use CTRL+F to search for the appropriate IDs. First, we need to run the below query, replacing the {ServicePrincipalID} with the ID we found in step 2.

HTML
GET https://graph.microsoft.com/v1.0/servicePrincipals/{ServicePrincipalID}/approles

After running the query, we get a list of all the Application Roles. Luckily, the output only shows 100 results. Now, use CTRL+F to search for the permissions mentioned above and write down the IDs.

Step 4: Assigning the Application Roles to the Managed Identity

To assign the Application Roles, we need the following IDs:

  1. PrincipalId (step 1)
  2. ResourceId (step 2)
  3. AppRoleIId (step 3)

To add the Application Roles to the Managed Identity, we need to use a POST query in combination with a Content-Type application/json in the header and the following request body:

HTML
POST https://graph.microsoft.com/v1.0/servicePrincipals/{your-graph-serviceprincipal-id}/appRoleAssignedTo

Content-Type: application/json

JSON
{
"principalId": "{your-managed-identity-service-principal-id}",
"resourceId": "{your-graph-serviceprincipal-id}",
"appRoleId": "{your-app-role-id}"
}

Repeat step 4 until you assigned all the Application Roles.

Step 5: Verifying the permissions in Entra ID

Now that we have set the Application Roles (Permissions), we can verify this within the Entra ID portal. To do so, we go to Entra ID, Enterprise Applications, then set the Application Type to “Managed Identities”.

Now, we select the Managed Identity we create to perform the Azure Automation tasks and go to Permissions. Here, we will see the permissions we just assigned.

Creating the runbook

Now that we have set all the appropriate permissions, we can start creating the runbook. For this case, we will look for users who are members of a certain security group called “CTX-DaaS-B2B” and verify if the Extension (Created in the previous blog) and CreationType are equal to Invitation. We will check this for each user who’s a member of the security group and check each setting, and if needed, we will apply changes.

Below, you can see the script we are using to automate the process of invitations and setting the extensions so the user can sign in to the Citrix Environment, which is mentioned in the previous blog.

PowerShell
Connect-MgGraph -Identity -NoWelcome
$Group = "CTX-DaaS-B2B"
$group = Get-MgGroup -Filter "DisplayName eq '$Group'"
$members = Get-MgGroupMember -GroupId $group.Id
$ExtensionUPN = "extension_c8ef4d2cb6694d76974c5a05be13affa_guestShadowUPN"
$ExtensionSID = "extension_c8ef4d2cb6694d76974c5a05be13affa_guestUserOnPremSID"
$CreationType = "Invitation" 
$RedirectURL = "https://newyard.Cloud.com"
$UserMessage = "You have been invited to the Citrix New Yard environment. `
                Redeem this invitation to get started and you’ll be redirected to the Citrix New Yard"

foreach ($member in $members) {
    $user = Get-MgUser -UserId $member.Id -Property ID, DisplayName, Mail, UserPrincipalName, UserType, CreationType, ExternalUserState, OnPremisesSecurityIdentifier

    if ($user.UserType -ne "Guest"){
        Write-output "User $($user.displayname) is Member, changing to Guest!"
        Update-MgUser -UserId $user.Id -UserType "Guest"
    } 

    $UserExtensions = (Get-mguser -UserID $user.Id -Property $ExtensionUPN).AdditionalProperties
    if ($UserExtensions.keys -NotContains $ExtensionUPN) {
        Write-Output "User $($user.displayname) has nu Extension, updating UPN and SID Extensions"
        Update-mguser -UserId $user.id -AdditionalProperties @{$ExtensionUPN = $user.UserPrincipalName}
        Update-mguser -UserId $user.id -AdditionalProperties @{$ExtensionSID = $user.OnPremisesSecurityIdentifier}
    }

    Write-output "$($user.displayname) has CreationType $($user.creationType)"
    if ($user.CreationType -ne $CreationType) {
        Write-output "User $($user.displayname) isn't invited, inviting user!"
        New-MgInvitation -InvitedUser $user -InvitedUserEmailAddress $user.Mail -InviteRedirectUrl $RedirectURL -SendInvitationMessage:$true -InvitedUserMessageInfo:@{CustomizedMessageBody=$UserMessage}
    }

}

disconnect-MgGraph

We published the runbook to production once we tested it (using the test pane in the PowerShell runbook) and verified everything was correct. This can be done using the Publish button inside the PowerShell runbook.

Create a Schedule

Now that we have the runbook published, we can start with scheduling the runbook. As we start migrating users to the new environment, we have set the schedule to run every hour. When the migration is done, we can change the schedule to once a day, as we normally don’t make that many changes or create multiple users a day.

Let’s start with creating the schedule. We go to the Schedules from the Automation account and add a schedule.

Once the schedule is created, we need to assign the runbook to the schedule. Therefore, we go to the runbook, click on the “Link to schedule” button, click on the link to a schedule in your runbook, select the correct schedule, and click OK. To verify if the runbook is successfully linked to the schedule, click “Schedules” within the runbook and verify that the correct schedule is linked.

Verify the Azure Automation Task

Now that we successfully have followed all the steps:

  1. Creating Azure Automation Account
  2. Add PowerShell Modules
  3. Set permissions
  4. Create the runbook
  5. Create and linked a Schedule

We can now see in the overview of the Azure Automation account if the runs are completed successfully. As you can see in the screenshot below, the job has run once and was successful. The users will receive an invite every hour unless they already received one.

Conclusion

We can conclude that Azure Automation is a powerful tool for automating repetitive tasks, such as sending invites to new guests. I couldn’t achieve this without the guidance of GoToGuy’s blog, where the Graph Explorer is explained to configure all the necessary permissions. I will undoubtedly utilize Azure Automation more frequently now that I know its capabilities.

Automating FAS Authorization Certificate renewal

As I deploy more and more Citrix FAS Servers for customers who intend to utilize Entra ID (formerly known as Azure AD) as their Identity Provider (IdP), I have observed that the FAS Authorization Certificate requires periodic renewal. To ensure that expired certificates do not inconvenience my customers and to maintain a level of automation and security, I have developed a simple script that notifies them when a new FAS Authorization Certificate is needed and mails them to issue it through their Active Directory Certificate Services (ADCS).

The current FAS Renewal script is designed to run locally on the FAS Servers. In the future, I plan to enhance it to allow for remote execution from a management server or similar setup. However, for the current version, you must follow these steps on all your FAS servers individually.

Automation and Security Considerations

In today’s technology landscape, automation is fundamental to organizational efficiency. However, maintaining a degree of control over critical components like your Certificate Authority is equally important. To strike a balance, I have designed this script to handle the certificate renewal process up to a certain point and then notify you via email, granting you the authority to issue the certificate from your ADCS.

Script Functionality

When I set out to create this script, my primary objective was to automate the entire certificate renewal process. The script follows a logical sequence of actions:

  1. Checking the Expiry Date: The script evaluates the expiration date of the current FAS Authorization Certificate.
  2. Requesting a New Certificate: If the current date approaches the expiration date, the script initiates a request for a new certificate.
  3. Connecting to ADCS: The script establishes a connection to your Active Directory Certificate Services (ADCS), ensuring that the certificate issuance process is orchestrated securely.
  4. Updating FAS Configuration: Once the new certificate is obtained, the script updates the Citrix FAS configuration to utilize the freshly issued certificate seamlessly.

Enhanced Security

During my research on certificate issuance using scripts, I came across discussions highlighting the importance of maintaining control over such processes. I agree with this perspective. The FAS Authorization Certificate is a Certificate Request Agent, which implies that it can potentially be used to request certificates for all users within your environment. In the event of compromise, this could pose a significant security risk, allowing unauthorized access to your organization’s resources. To mitigate this risk, I made a deliberate choice to implement a user-based approach. This ensures that the issuance of certificates is controlled and limited to authorized personnel, reducing the potential for misuse and bolstering the security of your organization’s infrastructure.

Prepare Mail User

In order for the script to successfully send emails to a predefined contact, it is crucial to ensure that the sent emails are delivered without being flagged as spam. To achieve this, authentication against the mail server is necessary. When sending unauthenticated emails through Office 365, there is a high likelihood that such emails will be routed to the recipient’s spam folder, resulting in them going unnoticed. While I have utilized my personal Office 365 account for this demonstration, it is possible to configure the script to work with an on-premises Exchange server as well. However, there are certain prerequisites that must be met to ensure successful user authentication for email transmission.

Account Setup

To establish a connection to a user’s mailbox, it is essential that the user account be enabled. Typically, shared mailboxes are configured as disabled users, but an enabled user account is required for this script to function effectively. Given that most organizations have password expiration policies, it is essential to use an Application Password to prevent script failures due to password changes. To create an Application Password, please refer to the provided link and ensure that you assign a meaningful name to it for identification purposes; in this example, “FASRenewalMail” was used.

App Password

Additionally, it is crucial to verify whether the user is permitted to connect to “Authenticated SMTP” by following the instructions provided in the linked resource.

Mail Configuration and Testing

Once the user meets all the specified prerequisites, we can proceed with configuring the mail functionality on the FAS servers. To ensure the best security and prevent passwords from being stored directly within the script, especially since scheduled tasks can run under different user accounts, we adopt a secure approach by creating an encrypted text file. This encrypted text file is the most secure method for password storage as it can only be accessed by the user who created it and exclusively on the machine where it was generated. Copying the file to another location or machine does not grant access to its contents.

To create the encrypted text file, please utilize the provided code snippet:

PowerShell
Read-Host -Prompt "Enter your tenant password" -AsSecureString | ConvertFrom-SecureString | Out-File "C:\Scripts\cred.txt"

File Location and User Considerations:

The encrypted text file has been saved to the directory C:\Scripts. If you prefer a different location, you have the flexibility to make that adjustment. However, it’s important to note that if the scheduled task is created under a different user account, you must initiate a PowerShell session under that specific user. Failure to do so will result in the script’s inability to retrieve the password, leading to script execution failure.

Testing the mail:

PowerShell
$TestUser = "[email protected]"
$Pass = Get-Content "C:\Scripts\cred.txt" | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $AdminName, $Pass

Send-MailMessageFrom $TestUser –To $TestUser –Subject "Test Email" –Body "Test SMTP Relay Service" -SmtpServer smtp.office365.com -Credential $cred -UseSsl -Port 587 

Once the encrypted text file has been successfully created, you can verify its functionality by conducting a test email transmission. Use the provided code snippet below for sending a test email, and remember to replace the $TestUser variable with the designated user you are using for this test:

If this testing phase proceeds without issues, you can proceed with the FAS Renewal Script implementation.

FAS Renewal Script:

The script relies on several configurable variables that must be set before execution to ensure its proper functioning. One critical variable to adjust is “ThresholdDays,” which should be tailored to your specific requirements. I recommend setting it to 30-60 days to ensure renewal well before the certificate expiration date. During testing, I used a value of 730 days, as newly created certificates are valid for 329 days. This extended period allows for multiple tests until you are satisfied.

Additionally, ensure that the “CredentialFile” variable points to the same location where you saved the encrypted text file in the previous step.

PowerShell
# Configuration
$ThresholdDays = 730
$AdminEmail = "[email protected]"
$ToEmail = "[email protected]"
$CredentialFile = "C:\Scripts\cred.txt"
$SmtpServer = "smtp.office365.com"
$SmtpPort = 587 
PowerShell
# Load Citrix FAS snap-in
Add-PSSnapin Citrix.A*

# Configuration
$ThresholdDays = 730
$SleepTime = 10
$AdminEmail = "[email protected]"
$ToEmail = "[email protected]"
$CredentialFile = "C:\Scripts\cred.txt"
$SmtpServer = "smtp.office365.com"
$SmtpPort = 587

# Get current certificate information
$CitrixFasAddress=(Get-FasServer)[0].Address
$DefaultCA=(Get-FasMsCertificateAuthority -Default).Address
$CurrentCertificate = Get-FasAuthorizationCertificate -FullCertInfo
$DaysDifference = ($CurrentCertificate.ExpiryDate - (Get-Date)).Days

# Load credentials from file
$SecurePassword = Get-Content $CredentialFile | ConvertTo-SecureString
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AdminEmail, $SecurePassword


# Function to send email notifications
function Send-EmailNotification {
    param (
        [string]$Subject,
        [string]$Body
    )

    Send-MailMessage -From $AdminEmail -To $ToEmail -Subject $Subject -Body $Body -SmtpServer $SmtpServer -Credential $Credential -UseSsl -Port $SmtpPort
}

if ($DaysDifference -lt $ThresholdDays) {
    # Request a new certificate
    $NewCertificate = New-FasAuthorizationCertificate -CertificateAuthority $DefaultCA -CertificateTemplate "Citrix_RegistrationAuthority" -AuthorizationTemplate "Citrix_RegistrationAuthority_ManualAuthorization"
    
    # Send email notification about pending certificate
    Send-EmailNotification -Subject "Pending FAS Authorization Certificates | $CitrixFasAddress" -Body "There is a pending FAS authorization certificate. Please issue the certificate on server $($CurrentCertificate.Address) so that FAS keeps working. After issuing, everything is automated and you receive a mail if everything was succesful."
    
    # Wait for certificate to be issued
    do {
        # Get all FAS authorization certificates
        $Certificates = Get-FasAuthorizationCertificate
        
        # Check if there are certificates with a status other than "Ok"
        $CertificatesNotOk = $Certificates | Where-Object { $_.Status -ne "Ok" }
        
        if ($CertificatesNotOk.Count -gt 0) {
            # Wait for a 1 hour before checking again
            Start-Sleep -Seconds $SleepTime
            Write-Output "Waiting for certificate to be issued"
        }
    } while ($CertificatesNotOk.Count -gt 0)

    # All certificates have status "Ok"
    Write-Output "All Certificates are Issued."

    # Remove the old Certificate and update the Certificate Definition
    Write-Output "Removing the old Certificate"
    Remove-FasAuthorizationCertificate -Id $CurrentCertificate.Id
    $CertificateDefinition = (Get-FasCertificateDefinition)[0].Name
    Write-Output "Updating the Certificate Definition with the new Certificate"
    Set-FasCertificateDefinition -Name $CertificateDefinition -AuthorizationCertificate $NewCertificate.Id
    
    # Send email notification about certificate renewal
    $CurrentCertificateNew = Get-FasAuthorizationCertificate -FullCertInfo
    $DaysDifferenceNew = ($CurrentCertificateNew.ExpiryDate - (Get-Date)).Days
    Send-EmailNotification -Subject "FAS Authorization Certificates Renewed | $CitrixFasAddress" -Body "The FAS authorization certificate is renewed. It's now valid for $($DaysDifferenceNew) days until $($CurrentCertificateNew.ExpiryDate)."
} else {
    Write-Output "Certificate expiration date is $($CurrentCertificate.ExpiryDate)"
}

After configuring these variables, save the script to the C:\Scripts directory.

Create a scheduled task.

Configure the scheduled task with a trigger that aligns with your customer’s preferences. I suggest selecting a workday for scheduling and avoiding Mondays, as mailboxes may be full, potentially causing the FAS Renewal Mail to go unnoticed. Configure the task to execute as follows:

Program: powershell.exe
Arguments: -ExecutionPolicy Bypass -File C:\Scripts\FASRenewalScript.ps1

FASRenewalScript Scheduled Task, Powershell

Use the same account that was used for creating the encrypted text file; otherwise, the script will not function as expected.

Conclusion

With these steps completed, you are now equipped to receive timely notifications when an FAS Authorization Certificate is approaching its expiration date. If you have any comments or questions, please feel free to reach out for further assistance.