Invoke-Command alternative for Intune-managed Windows devices

Continue reading if you want to have the ability to run PowerShell code (similar to Invoke-Command) against your Windows Intune-managed hybrid and cloud-only clients in a nearly real-time fashion and at the same time get the code results back ๐
I came across a thread on Reddit discussing the use of on-demand remediations in Intune to retrieve basic client data. However, I couldnโt find any existing PowerShell function for this purpose, so I decided to create one myself.
Meet my shiny, brand-new Invoke-IntuneCommand function (part of the IntuneStuff module).
Use cases
You want to run some one-time PowerShell code against your Windows devices ASAP
You want to get some Windows clients' data back (is a service running, is an application installed,...)
How does Invoke-IntuneCommand work?
New Intune remediation is created in the background, utilizing the code to be invoked within the detection component
Remediation is invoked via an on-demand request for each selected device
The function either waits for the remediation to complete or for the specified timeout to be reached (10 minutes by default, but can be overridden) in a which-comes-first manner
Remediation code results are retrieved (and converted to an object if the result is a compressed JSON string)
Remediation is deleted (this step can vary based on the used parameters and remediation invocation results)
The whole process can be nicely seen when Verbose parameter is used ๐

What does the Invoke-IntuneCommand output look like
Function Invoke-IntuneCommand returns a custom object for each device where the command was run.
The returned object contains several properties
DeviceId- managed device ID (Intune ID)DeviceName- name of the deviceLastSyncDateTime- when it contacted Intune the last timeProcessedOutput- returned output converted to an object if only a compressed JSON string was returnedOutput- string output of the invoked command (just the last line!)Error- thrown error if anyStatus- overall invoked remediation statuspending if not being run
fail if some error was thrown
success if everything was ok

If your code throws an error, you will see the error message in Error property and Status will be fail

Requirements
Module IntuneStuff
Graph permissions
DeviceManagementConfiguration.Read.All
DeviceManagementManagedDevices.Read.All
DeviceManagementManagedDevices.PrivilegedOperations.All
License to use Intune Remediation
Windows 10/11 Enterprise E3 or E5 (included in Microsoft 365 F3, E3, or E5)
Windows 10/11 Education A3 or A5 (included in Microsoft 365 A3 or A5)
Windows 10/11 Virtual Desktop Access (VDA) per user
Examples
Authenticate to Graph API with correct scopes
Connect-MgGraph -Scopes DeviceManagementConfiguration.Read.All, DeviceManagementManagedDevices.Read.All, DeviceManagementManagedDevices.PrivilegedOperations.All
Run some command against all clients
$deviceNameList = Get-MgBetaDeviceManagementManagedDevice -Filter "OperatingSystem eq 'Windows' and OwnerType eq 'company'" -all -Property DeviceName | select -ExpandProperty DeviceName
Invoke-Intunecommand -deviceName $deviceNameList -command "New-Item C:\temp2 -ItemType Directory" -Verbose
Create a folder on the selected clients
Invoke-Intunecommand -deviceName "PC-01" -command "New-Item C:\temp2 -ItemType Directory" -Verbose
In a few minutes (if the device is online) you should see an output similar to this one ๐

Hence folder C:\temp2 was created.
Check if the folder exists and return a simple string
$command = @'
if (Test-Path "C:\temp2") {
Write-Output "Folder exists"
} else {
Write-Output "Folder doesn't exist"
}
'@
$result = Invoke-Intunecommand -deviceName "PC-01" -command $command -Verbose
In a few minutes (if the device is online) you should see an output similar to this one ๐

Return an array of objects (of all running client PowerShell processes)
$command = @'
$result = Get-Process powershell | select processname, id
$result | ConvertTo-Json -Compress
'@
$result = Invoke-Intunecommand -deviceName "PC-01" -command $command -Verbose
# output the results
$result
# output just returned JSON string converted back to the object
$result.processedOutput
In a few minutes (if the device is online) you should see an output similar to this one ๐

Because we have converted the output to compressed JSON, it was automatically converted back to an object and saved in the ProcessedOutput property.
Maximize the amount of returned data by compression
If the output you need is over the 2048-character hard limit, you can reduce its length by compressing it using ConvertTo-CompressedString function (part of the CommonStuff module). This can make it 10x times smaller (but it depends on the output content solely)!
Invoke-Intunecommand function automatically tries to decompress the received output, so you don't have to worry about this part.
$command = @'
$result = Get-Process powershell | select processname, id
$result | ConvertTo-Json -Compress | ConvertTo-CompressedString
'@
# invoke selected command
$result = Invoke-Intunecommand -deviceName "PC-01" -command $command -Verbose
# output the results
$result
# output just returned compressed JSON string converted back to the object
$result.processedOutput
Adding command definition to the code block
Any command you run on the remote device needs to exist there. This specifically applies to the custom functions. You can add such a function directly to the invoked command like this
$command = @'
# Get-SomeData definition is inside the code block defined manually
function Get-SomeData {
do some stuff...
}
Get-SomeData | ConvertTo-Json -Compress
'@
Or use parameter prependCommandDefinition ๐ like this
$command = @'
# ConvertTo-CompressedString definition is added automatically thanks to 'prependCommandDefinition' parameter
$result = Get-Process powershell | select processname, id
$result | ConvertTo-Json -Compress | ConvertTo-CompressedString
'@
# text definition of the ConvertTo-CompressedString function will be added to the command, so it doesn't matter whether it is available on the remote system
Invoke-Intunecommand -deviceName "PC-01" -prependCommandDefinition ConvertFrom-CompressedString -command $command -Verbose
Limitations
Returned output is limited to 2048 chars (Intune inner limitation)
Only the last output line is returned
hence if you run the following code
write-output 'aaa'; write-output 'bbb'the returned output will be just
'bbb'!
Write-Hostcannot be used, instead useWrite-OutputIf the "helper" remediation is removed, any clients that have not already invoked it will be unable to do so
Tips
Definitely check the function help (
Get-Help Invoke-IntuneCommand -Full) to get more details & examplesTo get as much data back as possible, convert the output using
ConvertTo-CompressedStringfunction (part of the CommonStuff module) after converting it to the compressed JSON string. This way you can get 10x more data back. And theInvoke-IntuneCommandwill try to decompress it automatically ๐Invoke the function with the
Verboseparameter to obtain more detailed information, such as the remaining time until the deadline is reachedIf you wish to transform the result back into an object, ensure that your command returns a single result, specifically the compressed JSON
Get-Process powershell | select processname, id | ConvertTo-Json -Compress
If your command throws an error, the whole invocation takes more time, because a dummy remediation command (
exit 0) will be run too (because we are using remediation and if the detection part fails, the remediation part takes place)Helper remediations are named like
_invCmd_<yyyy.MM.dd_HH:mm>





