What AD Users have Logged in Recently - Number of Action Runs on a client

We have a baseline that adds software based on AD group membership. We also have a baseline that removes the same software if the logged on user is not in the AD group. The problem I am trying to solve is the case where two or more users share the same workstation. At least one of them is in the AD group, but others are not. In this case I don’t want to continually keep removing the software and then re-adding it. I would like to have relevance that will check the actionhistory.db, and if the removal program has run more than once in the past 30 days, then the removal is not relevant. Is this currently possible?

This statement lists all of the action history, but I am not sure how to integrate the relevance for count of number of time a give action/baseline ran and if it was in the last 30 days.

rows of statements "SELECT ACTION_HISTORY.ActionID, Issuedtime, ActionName, IssuerName FROM ACTION_HISTORY" of sqlite databases of files "ActionHistory.db" of folders "__Global" of data folders of client

Maybe there is a better way to solve this problem rather than using actionhistory.db.

1 Like

One option is to look at not just the “current user” of the system, but users that have logged in within the past X days as the mechanism to add or remove software, this way it is less likely to get removed from a different user logging in.

Even better if you can use the AD membership of the computer as the mechanism, rather than the AD membership of the user.

I think it is possible to see how many times an active action have run on the system using the action inspectors, but that is messy if the action gets stopped and started again. The ActionHistory.DB could be a better option.

Related:

What is the relevance to find all users of a system that have logged in within the past X days?

Like any good consultant, the answer is “it depends on your requirements”.
(And in re-reading the thread, I should have looked at the requirements more closely… Looks like context is around BigFix Actions)

This one is tricky…

ActionHistory.db will help with “how long ago was the SW installed” part of the question.
The hard part is “who’s account is the one that allows the software to be present, and has that user account logged into the Windows OS (either remotely or local keyboard) in the last 30 days”

1 Like

it depends, and is somewhat dependent upon the environment. Windows Only? or Cross Platform?

There are a few different ways to go about this, but not all of which are easiest to map to AD users.

Also, you might want to increase the BigFix AD User cache for this use case.

The best cross platform option is to look in the BigFix Client Logs for user logins, but that is going to be best if you increase the number and size of BigFix client logs from the default. I recommend 31+ days and 1MB in size.

See here: (these are old, but useful)

1 Like

What if your Action Script for the SW install recorded the user name logged in at the time the action ran…
Then your relevance could check either Windows Event Log or date stamps on NTUser.DAT files to check duration (or one of the methods @jgstew just contributed) to see if that specific user has logged on in the last 30 days. This would be in addition to the relevance you already have for checking that the user is logged on.

1 Like

This is a good idea, kind of related to: https://bigfix.me/fixlet/details/2533

Also kind of relates to this: bigfix-content/fixlet/Set InstallTimeUsers - Universal.bes at main · jgstew/bigfix-content · GitHub

I would recommend against almost any use of the Windows Event Log in applicability relevance. It is best suited for properties with infrequent report times, or in action substitution. The event logs can be so large, slow to parse, or just throw an error due to being too large or other issues.

1 Like

I think I like the idea of somehow trying to use the “Track Primary User” described here: https://bigfix.me/fixlet/details/2533 This method seems to have less overhead than searching through the client logs. Although, I am not sure how I take the logon history information and put it into the active directory relevance.

not exists elements of intersection of (set of ((names of groups of it as lowercase) of (logged on users of active directory)); set of ("Group_A";"Group_B";"Group_C"))
1 Like

It does have the overhead of requiring an action to do the tracking, unless the tracking is primarily performed by the existing actions that do the installs and uninstalls.

In my tests the client log parsing is less than 1 second. Not super fast, but not slow either.

You can actually slow down the reevaluation of applicability relevance to make it lighter weight by using the x-relevance-evaluation-period which is probably a good idea for this use case anyway. You can basically have it only evaluate if the software should be uninstalled once every X hours instead of every evaluation loop, and in a way, this is also part of what you are trying to achieve. (that said, I’m not 100% sure how the applicability relevance evaluation is handled once it is an action and no longer just the fixlet)

See this example: bigfix-content/fixlet/Test Fixlet Evaluation-Period.bes at main · jgstew/bigfix-content · GitHub


That is a challenge for any approach I think.

You would need to use local users of active directories since you are no longer concerned with the currently logged on user, but the recent logged on users compared to the active directory cached users.

Something I’m noticing is that names of local users of active directories is only giving me 1 result despite the fact that I see many AD users cached here: C:\Program Files (x86)\BigFix Enterprise\BES Client\__BESData\__Global\ADCache

Ideally, this relevance:

names of local users of active directories

would match the output of this relevance:

node values of xpaths "/User/Name/text()" of xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients

but at least in my case, it does not.


This will give the names of the cached AD users that have login records in the bigfix client logs:

elements of intersections of ( sets of node values of xpaths "/User/Name/text()" of xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients ; sets of unique values of (preceding text of first "'" of it | it) of following texts of firsts "User interface process started for user '" of locked lines containing "User interface process started for user '" of files whose(12=length of name of it AND name of it as lowercase ends with ".log") of folders "Logs" of folders "__Global" of data folders of client )

Also, I’m noticing this relevance is much faster when run from the client than through QnA in terms of reported execution time. (like 100x faster)

It would potentially be even more efficient to rely on the last modified time of the AD user cache data as a proxy for last login, but I don’t know if that is always going to be accurate, plus then you can’t also filter based upon number of logins, which I could with the relevance above.


This returns all of the AD groups of all of the cached AD users with logins recorded in the BigFix Client Logs:

unique values of node values of xpaths "/User/Groups/Group/Name/text()" of items 1 of (intersections of ( sets of node values of xpaths "/User/Name/text()" of xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients ; sets of unique values of (preceding text of first "'" of it | it) of following texts of firsts "User interface process started for user '" of locked lines containing "User interface process started for user '" of files whose(12=length of name of it AND name of it as lowercase ends with ".log") of folders "Logs" of folders "__Global" of data folders of client ), xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients) whose( exists (elements of item 0 of it, node values of xpaths "/User/Name/text()" of item 1 of it) whose(item 0 of it = item 1 of it) )

Then, putting it all together:

elements of intersections of ( it; sets of ("AD_GROUP_A";"AD_GROUP_B") ) of sets of node values of xpaths "/User/Groups/Group/Name/text()" of items 1 of (intersections of ( sets of node values of xpaths "/User/Name/text()" of xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients ; sets of unique values of (preceding text of first "'" of it | it) of following texts of firsts "User interface process started for user '" of locked lines containing "User interface process started for user '" of files whose(12=length of name of it AND name of it as lowercase ends with ".log") of folders "Logs" of folders "__Global" of data folders of client ), xml documents of files whose(name of it starts with "ADObjectUser_" AND name of it as lowercase ends with ".xml") of folders "__Global\ADCache" of data folders of clients) whose( exists (elements of item 0 of it, node values of xpaths "/User/Name/text()" of item 1 of it) whose(item 0 of it = item 1 of it) )

QnA reports that this takes about 200ms while when done through client eval, it reports less than 1ms of evaluation time. I did write this to be fairly efficient, but not sure why it is that fast honestly. My test system has 31 client logs, but they are rather small. I am only parsing the client logs once in the relevance though, so much larger log files should not have a massive impact on this. This relevance does read the AD User Cache XML twice, which might be able to be reduced to once with some effort. The relevance is also written to minimize “tuple expansion” by having at least one side be a single set.

I am trying the following relevance, but may end up using JG Stews method of looking at the client logs. I wanted to try the relevance below because it is less overhead than looking at the client logs and very straight forward.

not exists (rows of statements "SELECT ACTION_HISTORY.ActionID, Issuedtime, ActionName, IssuerName FROM ACTION_HISTORY where ActionName = 'MY ACTION'" of sqlite databases of files "ActionHistory.db" of folders "__Global" of data folders of client)

I was suggesting looking at the client logs to see which users logged in, which wouldn’t be in the actionhistory.db

If you want to know what number of actions have run on the client or other similar things about the actions on the client, then ActionHistory.db is the right place to look, partly because the client logs will only have action history over a short period of time.

With the help of SLB referenced in the link below, the following relevance checks to see if the uninstall action has been run in the past 30. If it has, the then not relevant. If it has run in more than 30 days, then relevant.

not exists (rows of statements "SELECT ACTION_HISTORY.ActionID, Issuedtime, ActionName, IssuerName FROM ACTION_HISTORY where ActionName = 'Pega/Blue Zone/ANA Uninstall' and IssuedTime > strftime('%25s', 'now') - 2592000" of sqlite databases of files "ActionHistory.db" of folders "__Global" of data folders of client)
1 Like

Thanks to brolly33 here: Relevance help; convert search and combine clauses

I changed the statement above to the following because it is easier to work with and change the number of days old since the action was taken. Additionally, the one above does not seem to be correct.

not exists (((column “IssuedTime” of it as string as integer) * second + “01 Jan 1970 00:00:00 -0000” as local time,column “ActionName” of it, column “IssuerName” of it, column “ActionID” of it) of rows whose (((it/day < 45) of (now -((column “IssuedTime” of it as string as integer) * second + “01 Jan 1970 00:00:00 -0000” as local time))) AND (column “ActionName” of it as string = “Pega/Blue Zone/ANA Uninstall”)) of statements (“SELECT IssuedTime,ActionName, IssuerName, ActionID FROM ACTION_HISTORY”) of sqlite databases of files “ActionHistory.db” of folders “__Global” of data folders of client)

@mbartosh I was looking for this post earlier this week. Thanks for the link.
Glad it helped you refine your query.