Microsoft Graph PowerShell SDK Cmdlets Support Custom Security Attributes
Updated 12-Sep-2023
Introduced in preview in December 2021, I’m still looking for a good way to use Azure AD custom security attributes. Microsoft recently updated conditional access policies to support an app filter based on custom security attributes. That’s a nice example of what’s possible, but it’s probably a scenario limited to tenants that need such a capability.
In my original commentary on custom security attributes, I wondered if they might replace Exchange Online custom attributes. So far, I see little sign that this will happen, if only because the preview implementation only supports user and service principal objects. Exchange Online supports custom attributes for all mail-enabled objects, meaning that these attributes are more flexible. Marking Microsoft 365 groups so that changes to their membership can be monitored is just one recent practical example of custom attributes in use.
Marking objects is one thing. Being able to find the marked objects is equally important. The cmdlets in the Exchange Online management PowerShell support server-side filtering against custom attributes to make it easy and fast to search against the attributes. It therefore seemed like a good thing to check how to search Azure AD objects using values stored in Azure AD custom security attributes.
Assigning and Finding Custom Security Attributes
Cmdlets in the Microsoft Graph PowerShell SDK that operate against Azure AD user accounts and service principals (for enterprise and registered applications) support the custom security attributes. Microsoft’s documentation explains the basics. For example, to add custom security attributes to a user object, populate a hash table containing the attribute names and values and use it as input to the Update-MgUser cmdlet.
In this case, the attributes come from a set called Employees (Figure 1) that contains attributes defined as string, Boolean, and integer data types. You can see in the PowerShell snippet how to define the data types for Boolean and integer attributes.
Figure 1: Custom Security Attributes in the Azure AD admin center
If you make a mistake with an attribute value, update the parameter with the correct value and run Update-MgUser again. To remove an attribute value, pass a blank value (something like ‘ ‘) rather than $Null. The lack of support for the $Null PowerShell variable is an SDK foible.
To check that the custom security attributes are in place, run the Get-MgUser cmdlet against the user account and fetch the CustomSecurityAttributes property:
Although it’s simple to retrieve the property, complexity lurks because the attribute values are stored in a system dictionary object called additionalProperties. Multiple sets of custom security attributes can be assigned to an object, so additionalProperties holds a separate hash table for each set. In this instance, we’re only interested in attributes from the Employees set, so we extract them into a separate hash table to make it more convenient to access individual attribute values. Each attribute that we want to use is easily fetched by a keyed lookup against the hash table as shown below.
$EmployeeData
Key Value
--- -----
@odata.type #microsoft.graph.customSecurityAttributeValue
Executive True
JobCode Principal
LegalEntity RA Ireland
DateOfHire 2-Nov-1983
EmployeeNumber 150847
$EmployeeData['JobCode']
I’m sure that the structure we’ve just navigated to find custom security attributes makes sense in terms of the way that Microsoft generates the cmdlets in the Microsoft Graph PowerShell SDK from the underlying Graph APIs. However, compared to the ease of access to data using other PowerShell cmdlets, it’s too complex and clunky.
Searching All User Accounts for an Attribute Value
This feeling is confirmed when trying to find users with a specific value stored in an attribute. This code finds all user member accounts in the tenant with at least one assigned license (to exclude accounts like those used for shared and room mailboxes). The code then loops through all users to find those with custom security attributes from the Employees set and checks if the job code attribute has a certain value.
[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" -ConsistencyLevel eventual -CountVariable Records -All -Property CustomSecurityAttributes, Id, DisplayName
ForEach ($User in $Users) {
If ($User.customsecurityattributes.AdditionalProperties['Employees'] -ne $Null) {
$EmployeeData = $User.customsecurityattributes.AdditionalProperties['Employees']
If ($EmployeeData['JobCode'] -eq "Principal") {
Write-Host $User.DisplayName "is a Principal" }
}
}
I can’t find a filter for the Get-MgUser cmdlet to search for a value in a single attribute. I’ll keep trying, but for now I’m left with the technique explained above.
Using Custom Security Attributes with Service Principals
The same approach applies to updating the service principal for an enterprise or registered app with a custom security attribute. This code searches to find a specific application and then updates two attributes. The only changes are in the cmdlets used and that the AppDepartment attribute supports the storage of multiple values. This means that we must identify that the attribute is a collection of strings and pass the strings in an array:
Finding applications marked with a certain attribute value follows the same path as explained above. Here’s what I did to find applications marked with IT in the AppDepartment attribute:
[array]$Apps = Get-MgServicePrincipal -All -Property CustomSecurityAttributes, DisplayName, Id, AppId
ForEach ($App in $Apps) {
If ($App.customsecurityattributes.AdditionalProperties['Applications'] -ne $Null) {
$AppAttributes = $App.customsecurityattributes.AdditionalProperties['Applications']
If ($AppAttributes['AppDepartment'] -eq "IT") {
Write-Host $App.DisplayName "is an IT application" }
}
}
Looking Forward to Change
I’m not writing off Azure AD custom security attributes (but if you want to store employee data, there are some standard attributes available for user objects). I’m sure that the Entra ID team has many plans to use these attributes in different ways and that the Microsoft Graph PowerShell SDK developers could make the attributes easier to work with. At least, I hope so.
Keep up to date with developments like Azure AD custom security attributes by subscribing to the Office 365 for IT ProseBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.
2 Replies to “Using PowerShell to Manage Azure AD Custom Security Attributes”
“To remove an attribute value, pass the $Null value.”
On removing a custom attribute via update-mguser – Oddly enough I can set an attribute to false but setting it to $null doesn’t remove it or change the attribute or it’s assignment just fyi.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
“To remove an attribute value, pass the $Null value.”
On removing a custom attribute via update-mguser – Oddly enough I can set an attribute to false but setting it to $null doesn’t remove it or change the attribute or it’s assignment just fyi.
Use a space instead of $Null (or null). It’s an SDK foible: https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/