Solved

Worklet (Windows) evaluation block not working reliably

  • 20 May 2023
  • 3 replies
  • 241 views

Userlevel 1
Badge

UPDATE: I just discovered Automox will always run Remediation when the policy is run manual - this is an extremely aggravating feature which makes testing evaluation code much more difficult.  My question to the Automox staff - doesn’t this do more damage than good, what is the actual purpose of this?  This makes testing policy very tedious, just like how running a policy manually will not display notifications the same as when run via schedule.  Something this impactful should be a warning on the worklet, not buried in documentation!  Please include something like the below as this causes such an unbelievable headache

 

It appears the worklet evaluation block (Windows) either doesn’t work, or I’m doing something wrong…

I’m building a BitLocker worklet and exit code 0 in the evaluation block will always run the remediation block.  According to the documentation, exit code 0 is meant to mark as compliant and exit code 1 is meant to trigger remediation, but clearly this is not the case.

See example below - I put the exact same condition in the remediation block that I put in the evaluation block, and it continues to remediation block and exits with exit code 0 there, but will not exit with exit code 0 in the evaluation block (the Write-Output in the activity log proves it’s always going to the remediation block).

Evaluation block:

Same condition in the remediation block except with a Write-Output:

Activity logs proving that it’s always being marked as requiring remediation and running in the remediation block (because the Write-Output is displaying):

Because the evaluation block is so unreliable, it seems I have to put all my conditions in the remediation block where exit codes are working properly - this is extremely inconvenient.  Also yes, the device in the screenshot is my test machine where the script returns the condition as expected when run locally.  I’m just glad I caught this before rolling this out to any production, as allowing remediation to run regardless of exit code in evaluation, would’ve led to some extremely unfortunate circumstance.

 

 

icon

Best answer by JohnG-Automox 23 May 2023, 16:33

View original

3 replies

Userlevel 3

Hi @Nvlddmkm!

As you noted, when you execute a Worklet manually (be it from the Device’s page, or from the Worklet itself) it will perform an immediate run of the Worklet’s Remediation Code.  The Worklet’s Evaluation code is not considered.

 

You will receive a disclaimer indicating you of this immediate policy execution:

Disclaimer from when the policy is ran from the Device page (Run on this Device).

 

Disclaimer when the policy is run manually from the Worklet

 

Regarding a Worklet’s Evaluation Code, it is only ran at the time of a device scan. During a scan, the Evaluation Code will test a condition on a device, and then trigger the Remediation Code depending on the Exit status. An Exit 0 means the device is complaint and does not need Remediation. Any non-zero Exit value (Exit 1 for example) means the the device is not complaint and will flag the device for Remediation.  The Remediation code will then run according to the Worklet’s schedule.

 

For Worklets that are executed manually and without a schedule, I recommend baking the Evaluation logic into your Remediation code. Instead of passing an Exit 1, you can simply pass your Remediation function so it executes the code. 

 

While this is documented in our Knowledge Base articles, I do like your suggestion of adding some verbiage that indicates explicitly that only the Remediation Code will run during a manual execution!  I’ve submitted your feedback to our Product team as a Feature Request and looped in your Customer Success Manager for visibility.

 

Thanks for your feedback!


Let me know if you have any other questions.

Userlevel 1
Badge

 

For Worklets that are executed manually and without a schedule, I recommend baking the Evaluation logic into your Remediation code. Instead of passing an Exit 1, you can simply pass your Remediation function so it executes the code. 

Hello @JohnG-Automox , 

I did end up wrapping my evaluation code into my remediation code for testing when manually run - I’m just always hesitant in trusting something to work when it’s a change to working code.  Trusting that the code will also work the same in the evaluation block, as it did in the remediation block, became somewhat of a ‘leap of faith’.  

I was curious, since you mention the evaluation code executes on device scan, is there a performance gain or benefit here that you’d recommend, vs. just slapping evaluation code in the remediation block?  For example, if I wanted the worklet to only run if the device can communicate with the domain, so I put the condition (code below) in the evaluation block - would it check this condition constantly throughout the day (assuming I set my worklet to run every day of every week/month)?  

The use-case being, some mobile devices may not connect to the VPN very often (so they can’t contact the domain), and I want to catch them in those rare instances where they connect - and when they do, I want this worklet to run.  Would the evaluation block be something reliable to check a condition more frequently?

# Validate if machine can communicate with domain
if (!(Test-ComputerSecureChannel))
{
Exit 0
}

Thanks,

Userlevel 3

Hi @Nvlddmkm,

 

My recommendation would be to have your Evaluation code just check for the Bitlocker compliance. When the device scans based off of its Group Scan interval, it will determine if the device is compliant against with your worklet’s evaluation code, and if not, it will then schedule the worklet to run Remediation based on the worklet’s scheduled.

The default scan interval is 24 hours. Meaning, the device will scan once per day to determine its compliance against the policies and worklets that are associated to its group. If you’d like to test more frequently for device compliance, you can lower the Group scan interval.  For example, in my environment I like to check for device compliance more frequently so I set the scan interval to once every 4 hours:

 

Any time you make a change to a policy or worklet, it is best practice to issue a scan to the device so it can re-evaluate its compliance against the new changes.  You can also issue scans to multiple devices by using out Bulk Actions option on the Device page.

 

If the device(s) is not compliant with your Evaluation code and you have a schedule associated with the worklet, you will see the policy’s icon change to Scheduled and the Next Patch Window set according to the schedule of the policy. This will show you when the device should receive it’s remediation:

 

Now to your script!   As I mentioned, I would keep the evaluation logic simple.  A device is either compliant with your desired state configuration or it is not.  Here’s a revised evaluation code you can use that will check if the device has Bitlocker enabled, and if not, flag it for remediation:

Evaluation Code:

# Validate OS drive is decrypted
$bitLockerVolume = Get-BitLockerVolume | Where-Object { $_.VolumeType -match 'operatingsystem' }
if ($bitLockerVolume.VolumeStatus -match 'encrypted')
{
Write-Output "Volume is already encrypted."
Exit 0
}

else
{
Write-Output "Volume is not encrypted. Flagging for remediation."
Exit 1
}

One caveat to note is that the results of a worklet’s evaluation code is not exposed in the Activity Log.  Despite this, I still like to include Write-Output ‘s on each step of the script for clarity purposes.

 

The remediation code will then include a re-evaluation check to determine if the device meets the additional eligibility criteria, and if the conditions are met, the rest of the script proceeds.  If the eligibility conditions are not met, this data is output to your Automox Activity Log for visibility.

Remediation Code:

# Declaring functions
function Test-DeviceEligibility
{
# TPM failsafe
$chkTpm = Get-Tpm
if (!($chkTpm.TpmPresent))
{
Write-Output "TPM is not present."
Write-Output "Device is not eligible for remediation."
Write-Output "Now exiting."
Exit 1
}

if (!(Test-ComputerSecureChannel))
{
Write-Output "VPN connectivity is not established."
Write-Output "Device is not eligible for remediation."
Write-Output "Ensure the device is on VPN and has domain connectivity first."
Write-Output "Now exiting."
Exit 1
}

# Eligibility checks passed
# Passing 0 return for remediation
return 0
}

function Enable-BitlockerEncryption
{
# Validate local recovery directory path
$recoveryKeysDir = "$env:PROGRAMDATA\RecoveryKeys"

if (!(Test-Path $recoveryKeysDir))
{
try
{
Write-Output "Recovery directory not found: Creating $recoveryKeysDir"
New-Item -ItemType Directory -Path $recoveryKeysDir -ErrorAction Stop > $null
}

catch
{
Write-Output "Error: Could not create recovery directory"
Write-Output $Error[0].Exception
Exit 1
}
}

# Gather decrypted drives based on $drive
$toEncrypt = Get-BitLockerVolume | Where-Object { $_.VolumeStatus -match 'decrypted' -and $_.VolumeType -match 'operatingsystem' }

# Encrypt collected drives
foreach ($d in $toEncrypt)
{
try
{
Write-Output "Encrypting MountPoint $($toEncrypt.MountPoint)"
Enable-BitLocker -MountPoint $d.MountPoint -RecoveryPasswordProtector -SkipHardwareTest -ErrorAction Stop > $null
$bitLockerRecoveryPasswordObj = (Get-BitLockerVolume -MountPoint $d.MountPoint).KeyProtector | Where-Object { $_.KeyProtectorType -match 'recoverypassword' }
$bitLockerRecoveryPasswordObj > $recoveryKeysDir\BitLockerRecoveryKey.txt
Write-Output "Recovery Key:" "ID: $($bitLockerRecoveryPasswordObj.KeyProtectorId) $($bitLockerRecoveryPasswordObj.RecoveryPassword)"
Exit 0
}

catch
{
Write-Output "Error: Failed to perform encryption on $($d.MountPoint)"
Write-Output $Error[0].Exception
Exit 1
}
}
}

# Perform evaluation check to determine device's eligibility.
Write-Output "Running evaluation."
$bitLockerVolume = Get-BitLockerVolume | Where-Object { $_.VolumeType -match 'operatingsystem' }
if ($bitLockerVolume.VolumeStatus -match 'encrypted')
{
Write-Output "The Volume is already encrypted and the device is compliant."
Write-Output "Now exiting."
Exit 0
}

else
{
Write-Output "The Volume is not encrypted. Device is not compliant."
Write-Output "Proceeding with device eligibility check..."
Write-Output ""
if ((Test-DeviceEligibility) -eq 0)
{
Write-Output "Device passed eligibilty check."
Write-Output "Running Enforce-BitlockerEncryption function."
Enable-BitlockerEncryption
}

else
{
Write-Output "Device did not pass eligibility check."
Exit 1
}
}


I broke up the remediation code into several functions so the script can be managed more easily.  The remediation code will re-run the evaluation logic, and if the device is not-compliant (Bitlocker is not enabled), it will then run the Test-DeviceEligibility function that determines if the Device has TPM enabled and has domain/VPN connectivity through the Test-ComputerSecureChannel cmdlet.

If the Test-DeviceEligibility function returns a 0 (meaning all checks are met), it will then run the Enable-BitlockerEncryption function to enable Bitlocker on the device.  If the function does not pass, the script will return an Exit 1 and end.  Since you are running this worklet on a schedule, it will retry again at the worklet’s next scheduled time.

 

The results of the remediation run can then be found in your Automox Activity Log for further analysis:

 

 

This is optional, but a cool trick you can perform is to enable Device Targeting on the Worklet.  If the device is a specific OS version, or if it has a certain IP address prefix, you can put the device into scope for the worklet run:

 

In other words, you could use Device Targeting to determine if the system is on VPN based on an IP range and do away with the Test-ComputerSecureChannel portion of the eligibility function.  Completely optional of course!


I hope this gives you what you need, or at the very least, gets you started!

 

Have a great day!

Reply