In the enterprise business unit where you need to audit package token activity, create a Data Extension (DE) with the following specifications:
Name: AppUser_LoginAudit
External Key: AppUser_LoginAudit
| Field Name | Data Type | Length | Primary Key | Nullable | Default Value |
| UserID | Text | 100 | No | No | Not Applicable |
| UserName | Text | 255 | No | No | Not Applicable |
| LastLogin | Text | 100 | No | Yes | Not Applicable |
| ApplicationID | Text | 50 | No | No | Not Applicable |
| AuditDate | Date | Not Applicable | No | No | Current Date |
| CreatedBy | Text | 50 | No | No | Not Applicable |
| DefaultBusinessUnit | Text | 50 | No | No | Not Applicable |
NOTE: This will include internal APP users which will not be visible in the Installed Package UI and can not be modified and also ALL Mobile Push App (located in Mobile Push Admin)
Packages that had a name updated will not reflect in the app user name, use applicationid to match these packages
(Note: LastLogin is set to a nullable Text field because if a user has never logged in, the API will not return a date value, and SOAP date strings are often easier to log as text before converting them later).
After the DE has been created, you will need to create an SSJS activity to query the AccountUser object and log the results. The script uses WSProxy to find any user whose Name contains "app user" and inserts their ID, Name, and LastSuccessfulLogin date into the Data Extension.
<script runat="server">
Platform.Load("Core", "1.1.1");
try {
var targetDEKey = "AppUser_LoginAudit";
var prox = new Script.Util.WSProxy();
// 1. Define the columns to retrieve from the SOAP object (Added 'ActiveFlag')
var cols = ["ID", "Name", "LastSuccessfulLogin", "CustomerKey", "Roles", "DefaultBusinessUnit", "ActiveFlag"];
// 2. Define the filter (Name contains 'app user')
var filter = {
Property: "Name",
SimpleOperator: "like",
Value: "app user"
};
var moreData = true;
var reqID = null;
var insertedCount = 0;
// Define the GUID regex pattern (case-insensitive)
var guidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
// 3. Loop through the AccountUser records
while (moreData) {
moreData = false;
// Use getNextBatch if paginating, otherwise retrieve the first batch
var res = reqID == null ? prox.retrieve("AccountUser", cols, filter) : prox.getNextBatch("AccountUser", reqID);
if (res != null && res.Status == "OK") {
// 4. Process the results and insert into the DE
for (var i = 0; i < res.Results.length; i++) {
var user = res.Results[i];
var userName = user.Name || "Unknown";
var isActive = user.ActiveFlag; // Extract the ActiveFlag property
// Check if the user name contains a GUID string
if (!guidRegex.test(userName)) {
var userId = String(user.ID);
var lastLogin = user.LastSuccessfulLogin || "Never Logged In";
var custkey = user.CustomerKey;
// Safely extract the DefaultBusinessUnit value
var defaultBU = user.DefaultBusinessUnit ? String(user.DefaultBusinessUnit) : "";
// Safely extract the CreatedBy value from the nested Roles array
var createdByValue = "";
if (user.Roles) {
// Handle standard WSProxy array serialization
var firstRole = user.Roles.length > 0 ? user.Roles[0] : null;
// Drill down to the Client object
if (firstRole && firstRole.Client && firstRole.Client.CreatedBy != null) {
createdByValue = String(firstRole.Client.CreatedBy);
}
}
// 5. Evaluate if CreatedBy != '0' AND ActiveFlag != false
// We cast isActive to a string and lowercase it to safely catch both boolean false and string "false"
if (createdByValue !== "0" && String(isActive).toLowerCase() !== "false") {
var insertProps = [
{ Name: "UserID", Value: userId },
{ Name: "UserName", Value: userName },
{ Name: "LastLogin", Value: lastLogin },
{ Name: "ApplicationID", Value: custkey},
{ Name: "CreatedBy", Value: createdByValue },
{ Name: "DefaultBusinessUnit", Value: defaultBU }
];
var insertResult = prox.createItem("DataExtensionObject", {
CustomerKey: targetDEKey,
Properties: insertProps
});
if (insertResult.Status == "OK") {
insertedCount++;
}
}
}
}
// 6. Check if there are more rows to fetch
if (res.HasMoreRows) {
moreData = true;
reqID = res.RequestID;
}
}
}
Write("Audit complete. Successfully logged " + insertedCount + " users to the Data Extension.");
} catch (error) {
Write("Error: " + Stringify(error));
}
</script>Run the above SSJS activity to query the AccountUser object and log the login details of any matching users within the DE AppUser_LoginAudit.
This script natively includes WSProxy's getNextBatch pagination method. Even if you have thousands of users that match your search string, the script will safely page through them in batches of 2500 until all matching users have been processed and logged.
005317493

We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.