# 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 😎

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">When I say "nearly real-time", I’m referring to a delay of just a few minutes, which, in the context of Intune, is practically instantaneous 😁</div>
</div>

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](https://www.powershellgallery.com/packages/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 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710938105078/d4ad985b-8ac3-4c9a-9f38-fc285bf74508.png align="center")

---

# 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 device
    
* `LastSyncDateTime` - when it contacted Intune the last time
    
* `ProcessedOutput` - returned output converted to an object if only a compressed JSON string was returned
    
* `Output` - string output of the invoked command (just the last line!)
    
* `Error` - thrown error if any
    
* `Status` - overall invoked remediation status
    
    * **pending** if not being run
        
    * **fail** if some error was thrown
        
    * **success** if everything was ok
        

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710937415749/1697d36d-0111-4fbd-8e29-c04e47f2b63a.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710934959325/fc852a9c-4837-4783-a67c-c1a6ad867658.png align="center")

---

# Requirements

* Module [IntuneStuff](https://www.powershellgallery.com/packages/IntuneStuff)
    
* Graph permissions
    
    * DeviceManagementConfiguration.Read.All
        
    * DeviceManagementManagedDevices.Read.All
        
    * DeviceManagementManagedDevices.PrivilegedOperations.All
        
* [License](https://learn.microsoft.com/en-us/mem/intune/fundamentals/remediations#licensing) 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

```powershell
Connect-MgGraph -Scopes DeviceManagementConfiguration.Read.All, DeviceManagementManagedDevices.Read.All, DeviceManagementManagedDevices.PrivilegedOperations.All
```

## Run some command against all clients

```powershell
$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

```powershell
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 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710933994195/b2c8bf24-cb1e-414f-8e88-d1fc3a1ee9da.png align="left")

Hence folder `C:\temp2` was created.

## Check if the folder exists and return a simple string

```powershell
$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 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710935443241/d9aecc86-19e9-4435-8c47-7becc5a60d72.png align="left")

## Return an array of objects (of all running client PowerShell processes)

```powershell
$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 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710936858290/0998dd94-488b-4735-8109-4ea35d0fe3fd.png align="center")

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](https://www.powershellgallery.com/packages/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.

```powershell
$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

```powershell
$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

```powershell
$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-Host` cannot be used, instead use `Write-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 & examples
    
* To get as much data back as possible, convert the output using `ConvertTo-CompressedString` function (part of the [CommonStuff](https://www.powershellgallery.com/packages/CommonStuff/) module) after converting it to the compressed JSON string. This way you can get 10x more data back. And the `Invoke-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 reached
    
* If 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>`
