PowerShell DCR Log Analytics: Part 2.8 – How the Collection Script Works

This post will cover how the data collection script works with a focus on the sections you would want to change in order to modify it. An example of how to modify it, along with the necessary corresponding DCR and table changes, will come later as I am trying to keep these posts less monstrously sized going forward.

Please note, this is all in reference to SampleCollectionScriptV3.ps1 until stated otherwise. Again, credit to the Jan Ketil Skanke of the MSEndpointMGR team as the methodology to get the actual data into a JSON/array was his handy work.

In this section we will cover…

  • Important Variables in the Script
  • Functions in the Script
  • How the Data is Assembled
  • Sending the Data to the Function App & Validation
  • Conclusion


The code itself is highly commented and is more detailed than the overview below.

Important Variables in the Script:

  1. $ScriptVersion – With the right query, this can tell you what devices (and count of devices) are using what version of the script. This value can be incremented to monitor what version is deployed and thus create queries that require at least X version of the script be in use.

  2. $AzureFunctionURL – This is the URL to call your Function. See previous posts for more information.

  3. $Table – The full table name (including _CL). See previous posts for more information.

  4. $DcrImmutableId – The DCR Immutable ID. See previous posts for more information.

  5. $WriteLogFile – Controls whether the JSON gets uploaded ($False) or written to a local file as sample data to create a new DCR ($True). See previous posts for more information.

  6. $Delay – Controls the random up to 50-minute delay on uploads. See part 2.7 for more details.

  7. $LogName – This value actually doesn’t matter. Consider it a friendly log name that gets logged in the Function App as well as used in the name when using WriteLogFile.


Functions in the Script:

  1. Get-AzureADDeviceID
    Gets the Azure device ID of the device the script is running on. Used for calling to the Function App.

  2. Get-AzureADJoinDate
    Gets the date the device was joined to Azure for use in the delay calculation.

  3. Get-AzureADTenantID
    Gets the ID of the tenant the device is enrolled in. Used for calling to the Function App.

  4. New-AADDeviceTrustBody, Test-AzureADDeviceRegistration, Get-AzureADRegistrationCertificateThumbprint, Get-PublicKeyBytesEncodedString, & New-RSACertificateSignature
    These are all used on the new authentication methods discussed at length here.


How the Data is Assembled:

Looking at the SampleCollectionScriptV3.ps1, the script really begins to do something on line 414 (#Get Common Data). Here we begin to query data from the system and store the results (after formatting as needed) into variables. This continues up to line 469.

For example, we might do the following to get the machine name.

$ComputerName = $Env:ComputerName



Lines 470-479 (seen below) are the most important lines for this post. These lines actually make the JSON, or rather the array members we then turn into a JSON. On each line we add a member to the array (what later becomes a column header in Log Analytics) and then populate that property with the contents of the variable and thus the data we queried in the prior section.

$Inventory | Add-Member -MemberType NoteProperty -Name "ManagedDeviceName" -Value "$ManagedDeviceName" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "AzureADDeviceID" -Value "$AzureADDeviceID" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "ManagedDeviceID" -Value "$ManagedDeviceID" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value "$ComputerName" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "ActiveUser" -Value "$users" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "ComputerUpTime" -Value "$ComputerUptime" -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "NetworkAdapters" -Value $NetWorkArrayList -Force
$Inventory | Add-Member -MemberType NoteProperty -Name "ScriptVersion" -Value "$ScriptVersion" -Force



Moving on to the next important piece, we have a bit of a confusing chunk of code. This section was created by yours truly and gets around a silly problem introduced by Microsoft.

#Format Data for Submission to DCR
$RawEvents = $DevicePayLoad 
$DevicePayLoad =  $DevicePayLoad | ConvertTo-Json
$DevicePayLoad = "[" + "
$($DevicePayLoad)
" + "]"



If you look at the article / code provided by Microsoft on how to upload PowerShell data to DCR’s, you will notice two things. Microsoft states this system requires PowerShell 7.2 and…

$body = $log_entry | ConvertTo-Json -AsArray;



…Their code uses the -AsArray switch of the ConvertTo-Json command. The -AsArray switch only exists in PowerShell 7.2 or higher. This is done because (for reasons I can’t explain) Microsoft changed the data formatting from the HTTP API to the DCR API in only one way, it must now have a “[” at the start and “]” at the end like the entire JSON is a singular JSON Array. Hence the name of the switch -AsArray.

Again, I can’t explain the why I just know the what. Now, I don’t really want to deploy PowerShell 7.2 to devices and calling PowerShell 7.2 to run a script through Proactive Remediations (or any part of Intune) is a trick. So, I simply do this…

$DevicePayLoad = "[" + "
$($DevicePayLoad)
" + "]"

…To achieve quite literally the same thing. Now we have our opening [, the data starts on the next line, and the last line is our ]. Note that the line breaks here are important. Feel free to compare this output to the output of -AsArray, it’s the same.

Moving on, this is where we start to incorporate the new authentication mechanism. Unlike prior versions, we start to compile a new array using the function New-AADDeviceTrustBody. This automatically creates an array with the necessary authentication fields and data. We then add to that array fields for our Table name, DCR ID, and the JSON we just created of our actual log information. As you can tell by my all-capital remark, I had an annoying time trying to figure out why this didn’t work on the first try.

# Create body for Function App request
$BodyTable = New-AADDeviceTrustBody

# Optional - extend body table with additional data
$BodyTable.Add("Table", "$($Table)")
$BodyTable.Add("DcrImmutableId", "$($DcrImmutableId)")
$BodyTable.Add("LogPayloads", $LogPayLoadApp) # NOTE THE DIFFERENT FORMATTING FOR THIS FIELD!!!! 



Sending the Data to the Function App & Validation:

We then either write the RAW & Full .JSON files (as controlled by $WriteLogFile) or, we perform the delay logic and then upload the FULL JSON to the Function App. Note that the final JSON conversion does not happen until right in the upload command now.

From there our Function App uses the added authentication information to validate the device. Once completed, it uses the DCR ID and Table name to determine where to send the data, pulls out the inner JSON of data that’s our actual data, and ships just that off to Log Analytics.

The Function App then kicks down a response code which is validated at the tail end of the script to determine a fail/success.

And that’s really all there is to it!

Obviously, there are sections I didn’t explain here. Feel free to review those in the code on your own if you really want to get knee deep in things.


Conclusion:

Now you should have a general understanding of how the script functions. Next, we will be modifying it to collect new data. Changing the script is easy, it’s everything else you have to do that makes that process long and thus drove me to make it its own post.


The Next Steps:

See the index page for all new updates!

Log Analytics Index – Getting the Most Out of Azure (azuretothemax.net)

Disclaimer

The following is the disclaimer that applies to all scripts, functions, one-liners, setup examples, documentation, etc. This disclaimer supersedes any disclaimer included in any script, function, one-liner, article, post, etc.

You running this script/function or following the setup example(s) means you will not blame the author(s) if this breaks your stuff. This script/function/setup-example is provided AS IS without warranty of any kind. Author(s) disclaim all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall author(s) be held liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the script or documentation. Neither this script/function/example/documentation, nor any part of it other than those parts that are explicitly copied from others, may be republished without author(s) express written permission. Author(s) retain the right to alter this disclaimer at any time. 

It is entirely up to you and/or your business to understand and evaluate the full direct and indirect consequences of using one of these examples or following this documentation.

The latest version of this disclaimer can be found at: https://azuretothemax.net/disclaimer/