Uninstall Application If Not Used In X Days

We would like to automate uninstalling an application if it has not been used in X number of days. For example, if MS Visio has not been used in 90 days, return True.

I have attempted to map out the headings for C:\Program Files (x86)\BigFix Enterprise\BES Client\LMT\CIT\app_usage_data:

Process Name First Used ? Last Used Has Usage Total Run Time Total Runs
VISIO.EXE Thu, 06 Oct 2016 05:46:33 -0500 Tue, 23 Jan 2018 06:16:23 -0500 Tue, 23 Jan 2018 13:35:05 -0500 TRUE 111 days, 04:31:41.124 422

Then using the code:
exists line whose ((((preceding text of first ";" of following text of first ";" of it) as time) <= (now - 90*day)) and ((preceding text of first ";" of it) as lowercase = "visio.exe")) of file (((pathname of parent folder of regapp "besclient.exe")) & "\LMT\CIT" & "\app_usage_data")

As you may see, I’m evaluating the “First Used” column and not the “Last Used” column as I would like; as I’m looking at text after the “first” “;”. I see that I cannot use “third” in the relevnace, so how do I look at the correct column?

Can you post the raw line out of the file so we can see where the semicolons line up?

Same as in the table I posted.

VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422

This data actually comes from the application usage summary inspector documented here:
https://developer.bigfix.com/relevance/reference/application-usage-summary.html

You should be able to determine the different fields by looking at the available inspector properties. You can also use the inspector directly in your relevance, if you want the most current data, e.g.

last time seen of application usage summary "visio.exe" <= (now - 90*day)
1 Like

@AlexaVonTess In the event that Steve’s suggestion doesn’t work (it didn’t on my 9.5 environment, even though the settings are present on my machines), try this:

(if (exists lines whose (it as lowercase starts with "visio.exe") of it) then (tuple string item 3 of (("( "& (concatenation " ), ( " of (substrings separated by ";" of (lines whose (it as lowercase starts with "visio.exe") of it)))) & " )") as time <= (now - 90*day)) else(false)) of file ((pathname of parent folder of regapp "besclient.exe") & "\LMT\CIT" & "\app_usage_data")

Steve’s solution is clearly better than parsing the text. But the question got me thinking about the text parsing puzzle in general, and for any future readers with a more generalized case I came up with a couple of methods of doing it. Both, however, required counting the number of parentheses in the expression.

The first way involves repeated use of following text of first ";" as in

q: ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422")
A: VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422
A: WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422
T: 0.034 ms
I: plural string

q: preceding texts of firsts ";" of following texts of firsts ";" of following texts of firsts ";" of following texts of firsts ";" of ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422")
A: Tue, 23 Jan 2018 13:35:05 -0500
A: Tue, 23 Jan 2018 13:35:05 -0500
T: 0.132 ms
I: plural substring

q: ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422") whose (preceding text of first ";" of it as lowercase = "visio.exe" and now - (preceding text of first ";" of following text of first ";" of following text of first ";" of following text of first ";" of it as time) > 90 * day)
T: 0.150 ms
I: plural string

The second method uses regular expressions. When I first started using regular expressions about a year ago I was hesitant and sometimes overwhelmed, but they do make for some very powerful text parsing, and can be a little more easily modified if (for example) the number of fields were to change. Here’s what I ended up with

q: (parenthesized parts 1 of it, parenthesized parts 3 of it) of matches(regex "^([^;]*)(;[^;]*){2};([^;]*);.*$") of ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422")
A: VISIO.EXE, ( Tue, 23 Jan 2018 13:35:05 -0500 )
A: WORD.EXE, ( Tue, 23 Jan 2018 13:35:05 -0500 )
T: 0.235 ms
I: plural ( substring, substring )

q: (parenthesized parts 1 of it, parenthesized parts 3 of it) of matches(regex "^([^;]*)(;[^;]*){2};([^;]*);.*$") of ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422")
A: VISIO.EXE, ( Tue, 23 Jan 2018 13:35:05 -0500 )
A: WORD.EXE, ( Tue, 23 Jan 2018 13:35:05 -0500 )
T: 0.231 ms
I: plural ( substring, substring )

q: exists (parenthesized parts 1 of it, parenthesized parts 3 of it) whose (item 0 of it as lowercase = "visio.exe" and now - (item 1 of it as time) > 90 * day) of matches(regex "^([^;]*)(;[^;]*){2};([^;]*);.*$") of ("VISIO.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422";"WORD.EXE;Thu, 06 Oct 2016 05:46:33 -0500;Tue, 23 Jan 2018 06:16:23 -0500;Tue, 23 Jan 2018 13:35:05 -0500;TRUE;111 days, 04:31:41.124;422")
A: False
T: 0.267 ms
I: singular boolean

an explanation for the regex "^([^;]*)(;[^;]*){2};([^;]*);.*$" is in order.
I’m building up three sets of parenthesized matches, so I can refer to them as “parenthesized parts 1”, “parenthesized parts 2”, and “parenthesized parts 3”.

^([^;]*) --> matches the first field, from the start of the line until the first semicolon.

This is followed by (;[^;]*){2} --> matches two semicolon-delimited fields - the two time fields that we want to discard.

That’s then followed by ;([^;]*);.*$. This matches the third dated field and through the rest of the line, keeping only the time/date itself inside the parentheses (discarding the leading and trailing semicolons).

That’s great that there are inspectors! It looks through our deployment of BFI, we have the correct BES Client Settings. I’m going to assume that the setting -:noapp: means I’m including all and excluding none.

BFI

It looks like you also need to use Local Client Evaluator in the Debugger.

It also looks like it does not parse the app_usage_data file, as those numbers do not match. I’m also going to assume that the evaluator is more accurate and is the actual process that pipes out the info to the app_usage_data file in the first place which is then imported into the BFI database. Lots of assumptions! :yum:

For example:
notepad.exe;Thu, 13 Apr 2017 07:10:17 -0500;Wed, 17 Jan 2018 08:54:41 -0500;Wed, 17 Jan 2018 09:14:48 -0500;False;00:29:47.842;7

Q: last time seen of application usage summary “notepad.exe”
A: Thu, 25 Jan 2018 15:18:34 -0500
T: 0.055 ms

I’m going to test further with this and thanks to everyone else who provided some information as well!

I had found this article a while back that uses automatic groups and the application usage data from BFI to automate the uninstalls that may be of some use. It also uses Visio in it’s examples.

1 Like

Interesting Read. Thank you. I think I’m going to do an Analysts for the various Apps which will evaluate each day and simply return “True” if not used in 90+ days.

Yes, your assumptions are correct. The BFI software scan task updates the app_usage_data file with the current inspector data at that time (you can see the relevance it uses at the very bottom of the task). And the inspectors are only available in client context.

Thanks for confirming. I created an analysis to provide True or False based on usage.

For example:

(exists keys whose (exists value "DisplayName" whose (it as string as lowercase contains "microsoft visio standard 2016") of it) of keys "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" of (x64 registries;x32 registries)) AND (last time seen of application usage summary "visio.exe" <= (now - 90*day))

What I’m seeing is a high percentage of “error” being returned. When I run the debugger on the endpoint, it says “non existent…”, even though Visio is alive and well. What I found was that VISIO.EXE was not listed in the app_usage_data file.

That tells me it is looking at that file perhaps (I guess I can prove that by removing an existing entry before and after). The bigger question, why isn’t it there? Visio has been installed since the last scan and the scan seems to have completed… I’ll have to dig into it more.

Remember the app_usage_data file is populated by the inspectors, so that is not a cause, just confirming that visio.exe is not in the application usage summary data. That means that it has never been launched (since app tracking was started), so you’ll want to check for existence also

exists application usage summary "visio.exe"

Ah! So to even appear in app_usage_data, it has to be launched at least once. That’s good info! Thank you.

I’m sure there is a neater way to write this but this works:

if exists keys whose (exists value "DisplayName" whose (it as string as lowercase contains "microsoft visio standard 2016") of it) of keys "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" of (x64 registries;x32 registries) then if exists application usage summary "visio.exe" then (last time seen of application usage summary "visio.exe" <= (now - 90*day)) else True else False

1 Like

I haven’t enabled usage tracking, but I expect this would work…

exists application usage summary "visio.exe" whose (last time seen of it <= (now - 90*day))

1 Like

Thanks. That wouldn’t work because if there is no usage, that means Visio.exe was never opened (aka 0 uses). I need that to be true and included for not being used in 90+ days. The code itself is correct otherwise, so thanks!

1 Like

Oh I see.

not exists application usage summary “visio.exe” whose (last time seen of it <= (now - 90*day))

Combine with your “visio is installed” relevance. If there is no usage, or the usage is older than 90 days, this is relevant.

You’ll also want to check the installation date of visio, else you may remove it right after installation before the user has a chance to execute it.

1 Like

That works and looks a lot cleaner. Thanks. Yes, I was thinking the same thing yesterday; about not wanting to uninstall right after an installation. I’ll look into that.

A post was split to a new topic: Setting Install Date reg key after app install

Update. The not was in error and was returning True on legitimate systems. I didn’t catch this until now (as I’m starting to actually utilize this code). Our objective was not to return an error in the analysis if there was no application usage summary for “visio.exe”.

The end goal being:

False if Visio is not installed
False if Visio was never run (no app usage)
False if Visio was run within 90 days
True if Visio if Visio exists and has not been run within 90 days

This should be the final code (for Visio Std), taking in to account a True value if no app usage exists:

if exists keys whose (exists value "DisplayName" whose (it as string as lowercase contains "microsoft visio standard 2016") of it) of keys "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" of (x64 registries;x32 registries) then if exists application usage summary "visio.exe" then last time seen of application usage summary "visio.exe" <= (now - 90*day) else True else False

Is there also inspector for the application usage path?