Table of contents
- Use cases
- How does Invoke-IntuneCommand work?
- What does the Invoke-IntuneCommand output look like
- Requirements
- Examples
- Authenticate to Graph API with correct scopes
- Run some command against all clients
- Create a folder on the selected clients
- Check if the folder exists and return a simple string
- Return an array of objects (of all running client PowerShell processes)
- Maximize the amount of returned data by compression
- Adding command definition to the code block
- Limitations
- Tips
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 JSON string)
Remediation is deleted (this step can vary based on 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 object if compressed JSON was returned originallyOutput
- string output of the invoked command (just 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 chars 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 code output line is returned
if you run the following code
write-output 'aaa'; write-output 'bbb'
the returned output will be just
'bbb'
!
Write-Host
cannot be used, instead useWrite-Output
If 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-CompressedString
function (part of the CommonStuff module) after converting it to the compressed JSON. This way you can get 10x more data back. And theInvoke-IntuneCommand
will try to decompress it automatically ๐Invoke the function with the
Verbose
parameter 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>