Loading

Marketing Cloud Engagement - Audit Installed Packages for last successful login date

Julkaisupäivä: Jul 10, 2026
Kuvaus

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 NameData TypeLengthPrimary KeyNullableDefault Value
UserIDText100NoNoNot Applicable
UserNameText255NoNoNot Applicable
LastLoginText100NoYesNot Applicable
ApplicationIDText50NoNoNot Applicable
AuditDateDateNot ApplicableNoNoCurrent Date
CreatedByText50NoNoNot Applicable
DefaultBusinessUnitText50NoNoNot 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>
Ratkaisu

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.

Knowledge-artikkelin numero

005317493

 
Ladataan
Salesforce Help | Article