Microsoft made a big step forward in the Modern Management field. Limitations like custom configurations or even Win32 App installs can be addressed now. Microsoft developed an EMS agent (aka SideCar) and released it as a new Intune feature called Intune Management Extension. This agent is able to manage and execute PowerShell scripts on Windows 10 devices and it does this quite well.

We can simply upload our own PowerShell scripts for device configuration:

Part 2 of this article answers common questions I’ve seen, when working with the Intune Management Extension – Part 2, Deep dive Microsoft Intune Management Extension – PowerShell Scripts.
Part 3 of this series will dive into the Microsoft Intune Management Extension – Win32 Apps deployment capabilities – Part 3, Deep dive Microsoft Intune Management Extension – Win32 Apps
How do we get the agent installed on the devices?
The ability to deploy basic MSI packages via MDM OMA-DM channel, is provided by Microsoft since the beginning of Windows 10. This is achieved by using the EnterpriseDesktopAppManagement CSP.
Microsoft is using this mechanism to deploy the agent to Windows 10 devices. Beginning with Windows 10 Version 1607 we have support of the Intune Management Extension now. The device must be AAD joined and the automatic MDM enrollment must be enabled (see Prerequisites).
UPDATE: Intune In-Development announcement March 2020
PowerShell scripts support for BYOD devices. PowerShell scripts will support Azure AD registered devices in Intune. This functionality does not support devices running Windows 10 Home edition.
The workflow is basically like this. If a PowerShell script is assigned to a user group (device groups are not supported since 22th of Oct.) and the agent is not installed, it will be pushed down automatically to the device via EnterpriseDesktopAppManagement CSP by Intune. Microsoft Intune network requirements and endpoints that must be reachable can be found here. This can be verified and traced in the “Advanced Diagnostics Report” of the MDM management.
Open Settings > Accounts > Access work or school > Connected to TenantName’s Azure AD > Info > scroll down to the bottom and click “Create report”

Now you get an report about all MDM configurations including the desktop app installations. Here we can see the EnterpriseDesktopAppManagement msi install of the EMSAgent with the ProductCode: {25212568-E605-43D5-9AA2-7AE8DB2C3D09}

./device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7B25212568-E605-43D5-9AA2-7AE8DB2C3D09%7D
The instruction can be translated into more human readable form with ProductCode and curly brackets, as the yellow marked resource uses URL encoding and here %7B = { and %7D = }
./device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/{25212568-E605-43D5-9AA2-7AE8DB2C3D09}
If the device encounters any error during installation of the agent, we can troubleshoot this with the eventlog:
Start event viewer > Applications and Services Logs > Microsoft > Windows > DeviceManagement-Enterprise-Diagnostics-Provider > Admin
An example where the agent installation went wrong with an error event id 1924 looks like this:
Event ID 1924, Error EnterpriseDesktopAppManagement CSP: An application install has failed. Examine the MSI log (C:\Windows\system32\config\systemprofile\AppData\ Local\mdm\{25212568-E605-43D5-9AA2-7AE8DB2C3D09}.log) for more details. Install command: ("C:\Windows\system32\msiexec.exe" /quiet /l*v C:\Windows\system32\config\systemprofile\AppData\Local\mdm\ {25212568-E605-43D5-9AA2-7AE8DB2C3D09}.log /qn /i "C:\Windows\system32\ config\systemprofile\AppData\Local\mdm\ {BD19E4D8-D6C9-48B2-A53C-579D03B29FE9}.msi" ), MSI ProductCode: {25212568-E605-43D5-9AA2-7AE8DB2C3D09}, User SID: (S-0-0-00-0000000000-0000000000-000000000-000), Result: (User cancelled installation.).
Here we can see the original MSI install command including logfile path for further analysis:
C:\Windows\system32\config\systemprofile\AppData\Local\mdm\ {25212568-E605-43D5-9AA2-7AE8DB2C3D09}.log
The agent will get installed in the 32-bit Program Files path:
C:\Program Files (x86)\Microsoft Intune Managment Extension

How does the agent gets it’s policy?
The agent will start to figure out the service endpoint via Service Discovery. A common approach in Microservices architecture in modern cloud platforms. After getting the Intune Service URL (https://somemssubdomain.manage.microsoft.com/SideCar/StatelessSideCarGatewayService) the agent can start to communicate and will receive its assigned policies.
All this can be traced in the logfiles here:
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs

The agent will start to download and execute the assigned PowerShell script here:
C:\Program Files (x86)\Microsoft Intune Managment Extension\Policies\Scripts\UserGUID_ScriptGUID.ps1
and the results are written here:
C:\Program Files (x86)\Microsoft Intune Managment Extension\Policies\Results\UserGUID_ScriptGUID.output|.error|.timeout
After successful execution the script and results are cleaned up and nothing is left on the device. Corresponding registry entries for the assigned scripts and execution results are here:
HKLM\SOFTWARE\Microsoft\IntuneManagementExtension\Policies\UserGUID\ScriptGUID

How often does the agent check for new policies?
The default is every 60 minutes.
What are the PowerShell script execution options?
We can execute scripts in system context or user context. Optional we can enforce a signature check.

The current file size limit is 10KB for ASCII scripts and 5KB for unicode scripts. The current file size limit is max. 200KB.
How do I enforce next agent check for policies?
Simply restart the Windows Service “Microsoft Intune Management Extension”.

Is there a particular order in which multiple scripts are executed?
No, they are executed in random order.
What can be done with the PowerShell script execution?
Some examples and potentially pitfalls with the usage of the Intune Management Extension are listed below:
Install Win32 application
Install Win32 applications can be done easily by using a web request to get sources for the installation and finally execution of the sources. This technique is used a lot in the Chocolatey space. A simple example with 7-zip is shown below and can be modified to your needs:
# Author: Oliver Kieselbach # Date: 11/28/2017 # Description: Download executable or msi and execute to install. # The script is provided "AS IS" with no warranties. $url = "http://www.7-zip.org/a/7z1701-x64.msi" $filePath = "c:\windows\temp\7z1701-x64.msi" $ProgressPreference = 0 Invoke-WebRequest $url -OutFile $filePath -UseBasicParsing & $filePath /quiet
To use Chocolatey you should follow the install instructions from the Chocolatey website:
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
After Chocolatey Framework installation you can use choco.exe install Package in additional PS scripts to install Chocolatey software. An excellent post from Peter van der Woude regarding Chocolatey and Intune Management Extension can be found here: Combining the powers of the Intune Management Extension and Chocolatey
Write registry keys in x64 hive and not WOW6432Node
As the agent is an 32-bit agent every PowerShell script execution will be in the 32-bit agent process. If we write a registry key on a x64 device from a 32-bit process it will be redirected to the WOW6432Node in the registry. This is often not the desired behavior. To solve this we can restart the script as a 64-bit process for script execution. An example to write a registry key to the x64 hive with this technique is shown below:
# Author: Oliver Kieselbach # Date: 11/28/2017 # Description: Write to registry and ensure execution from x64 process environment. # The script is provided "AS IS" with no warranties. Param([switch]$Is64Bit = $false) Function Restart-As64BitProcess { If ([System.Environment]::Is64BitProcess) { return } $Invocation = $($MyInvocation.PSCommandPath) if ($Invocation -eq $null) { return } $sysNativePath = $psHome.ToLower().Replace("syswow64", "sysnative") Start-Process "$sysNativePath\powershell.exe" -ArgumentList "-ex bypass -file `"$Invocation`" -Is64Bit" -WindowStyle Hidden -Wait } Function New-RegistryKey { Param([Parameter(Mandatory = $true)] [string]$Key, [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string]$Value, [ValidateSet("String", "ExpandString", "Binary", "DWord", "Multistring", "QWord", ignorecase=$true)] [string]$Type = "String") try { $subkeys = $Key.split("\") foreach ($subkey in $subkeys) { $currentkey += ($subkey + '\') if (!(Test-Path $currentkey)) { New-Item -Type String -Path $currentkey | Out-Null } } Set-ItemProperty -Path $currentkey -Name $Name -Value $Value -Type $Type -ErrorAction SilentlyContinue } catch [system.exception] { $message = "{0} threw an exception: `n{0}" -f $MyInvocation.MyCommand, $_.Exception.ToString() Write-Host $message } } if (!$Is64Bit) { Restart-As64BitProcess } else { # Enable Potentially Unwanted Application protection New-RegistryKey -Key "hklm:\SOFTWARE\Microsoft\Windows Defender" -Name "PUAProtection" -Value "1" -Type DWord }
Please see my GitHub account for Intune Management Extension Samples for a enhanced and maintained PowerShell Template (IntunePSTemplate.ps1) which takes care of x64 restarting, logging and so on!
UPDATE
With the Intune February 18, 2019 Update the ability to run a script in a 64-bit process was added. The template script to restart in a 64-bit process is therefore not necessary anymore when running PowerShell scripts with Intune, but in case of Win32 apps and potential install wrapper scripts, it might still be necessary to re-start the wrapper for the Win32 apps installation.

Execute in user context and show dialog
To show a simple example to execute scripts in user context we can use the following script to present a Message Box to the user:
Add-Type -AssemblyName PresentationFramework [System.Windows.MessageBox]::Show('Hello World from Sid-Car!')

I hope this explains the technical implementation of the Microsoft Intune Management Extension and the extension addresses some road blockers you might have in the past with Intune regarding advanced configurations.
See also my follow up post Part 2, Deep dive Microsoft Intune Management Extension – PowerShell Scripts for even more details and common questions around architecture and troubleshooting.
Part 3 of this series will dive into the Microsoft Intune Management Extension – Win32 Apps deployment capabilities – Part 3, Deep dive Microsoft Intune Management Extension – Win32 Apps
Great post Oliver. FYI file size limit has now been increased to a more usable 200kb :-).
Thanks, this is great news! I’ve updated the blog post accordingly.
I created a PS1 script with only the command , new-localuser -name Peter , in the log file i see ‘new-localuser’ is not recognized.
Second PS1 script with only , get-proces , runs fine.
Any idea’s why the first is failing ?
Script is set to run in SYSTEM context.
Yes, I think you need to run it from 64-bit process. Try to run “Get-Command Get-LocalUser” from a x86 PowerShell and you will see Get-LocalUser is not recognized as cmdlet. Running from x64 will succeed. To accomplish New-LocalUser execution, try my script example “writing to x64 registry hive”. This will re-launch the process in x64 and the cmdlet should succeed.
Hi Olivier,
Thanks a lot that must be it!
Will try your solution and script (im not good at scripting though 🙂 )
Is this correct script for creating a new local user?
Param([switch]$Is64Bit = $false)
Function Restart-As64BitProcess
{
If ([System.Environment]::Is64BitProcess) { return }
$Invocation = $($MyInvocation.PSCommandPath)
if ($Invocation -eq $null) { return }
$sysNativePath = $psHome.ToLower().Replace(“syswow64”, “sysnative”)
Start-Process “$sysNativePath\powershell.exe” -ArgumentList “-ex bypass -file `”$Invocation`” -Is64Bit” -WindowStyle Hidden -Wait
}
Function New-LocalUser
{
New-LocalUser “Oliver”
}
First of all: I do not recommend transferring secure data like passwords via this approach! But I know it’s done out there. The GPOs are often used for local user management tasks too (that approach uses a simple xml file with the password base64 encoded -> this is not secure!). Please consider something that was build for that purpose. Admin account management should be done via LAPS or other third party. I know there is no equivalent solution like LAPS for a cloud only environment right now. But the approach below is not secure and should therefore used within a test environment only!
The modified script below will work but be aware that this should not be used in production! The script content will be logged to the IntuneManagementExtension.log file. There you will find the clear text password as defined in the script. As an example I have copied the particular log file entry below the script to show that:
########################
This version does even use error codes. But for now these error codes are not reflected in the UI in Intune. As long as the script could be executed through the agent the Intune UI will show success at the moment.
Oliver, thanks a lot. This helps me a lot! Also usable for future script now i know where to place the PowerShell commandlets in the script.
I’m happy to help. But remember the technique used will not report errors thrown by the x64 script part. The script error code seen by Intune is always the x86 part and this should always be successful. The initial script is started as x86 and the one with the logic will spawn another process in x64 environment. if I find time I will see if I can enhance the script to catch the error code from the x64 part.
Hi Great article, shame it doesn’t apply to Hybrid Joined machines.
Hi – great articles and really helping with my own Intune setup.
I’m using a modified version of the script to fire up 64-bit powershell and I’m trying to automate OneDrive for Business folder redirection using examples I’ve found that use SHSetKnownFolderPath, which is loaded via:
$Signature = @’
[DllImport(“shell32.dll”)]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
‘@
$Type = Add-Type -MemberDefinition $Signature -Name ‘KnownFolders’ -Namespace ‘SHSetKnownFolderPath’ -PassThru
Problem I have is the call to ‘Add-Type’ fails with ‘Cannot add type. Compilation errors occurred.’
Script works fine when run manually. Is there anything else that needs to be set / enabled in the PowerShell process that will enable Add-Type to load this type?
Spontaneous I would say this may be a problem due to execution in user mode. In user mode it may be that the reason for this is PowerShell Constrained Language Mode (CLM). For example if you have AppLocker then you might have PowerShell CLM. During your tests is the script running in the logged on user context without elevation? At the moment I’m not sure if this would lead to an Compliation Error. I assume it should give you another error. So I’m not sure what exactly goes wrong. I’ll do some tests for my own to see if I can find something.
So very interesting I tried to run your snipped right now and it threw an error “Compliation errors occured”. I did a short review and I have seen a wrong quote. I replaced the skewed ones [DllImport(“shell32.dll”)] with straight ones ” and then it runs. Are you sure that it’s not a trivial copy and paste error.
So, hanging head in shame – yes that appears to be the issue! I was editing the file in Notepad++ and the double-quotes looked ok, but I overtyped them, saved it, uploaded and worked fine. Hmm..
Don’t understand why the script was running ok interactively, perhaps I was testing another version.
I suppose the main observation is how painful this is currently to debug when there is no simple way to get the system under test to just pull and run an updated file (it’s a reboot and wait..).
As an aside – presume it would be possible to wrap a script with something that would run it under the same context as the script pulled from Intune, so it could be tested (and debugged!) locally before uploading?
Anyway – much appreciated, I now have scripts that do the folder redirection for OneDrive for Business and another that hides the personal OneDrive from Windows explorer (which was confusing my users).
Glad to hear that it is working for you now!
[…] run it under the same context as the script pulled from Intune […] are you referring to execute scripts in system context?
If I understand it that way, this can be achieved by running psexec -i -s cmd then you have a commandline running in system context which is extremely useful for testing.
Oliver,
Thank you very much indeed for the article.
You wrote that the agent will be checking for the policies every hour. Does it mean that all assigned scripts should run every hour?
In my lab, I can see that the script has executed only once. After that, I’ve tried to re-run the agent, reboot the machine and waited for more than 6 hours, but it never runs again.
Do you know if it’s by design?
Hi Yaro,
first of all thank you! And no we do not have a runSchedule and the agent does not execute the script every hour at the moment. The agent simply checks for new policies in the backend – no re-run if a script was successful executed. If you like to re-run a scripts I suggest to register it in scheduled tasks via PS commands. That’s the only way at the moment.
you could write your script to copy your re-run script to a certain folder like C:\ProgramData\CustomScripts\myScript.ps1 and then register a scheduled task to run it periodically.
Utilize something like this:
some documentation regarding PowerShell and scheduled tasks:
https://docs.microsoft.com/en-us/powershell/module/scheduledtasks/register-scheduledtask?view=win10-ps
But you need to think about a strategy how to track all this or modify in the future. Once set you may want to have a modification script to change schedules or even delete it from the device again.
Hi Oliver,
Thanks for the idea and the script.
I have to use PowerShell scripts to distribute an application to end users.
Obviously, Intune Apps gives more control on this, but unfortunately, the application uses setup.exe in the process.
I don’t want to use a scheduled task as the app should be installed only once.
I’m working with the application vendor to see they can give me single-MSI file version of the app.
Hi Yaro,
see my example PS Script to solve such installs in section “Install Win32 application”. You can use a web storage like Azure Blob Storage or any other public storage and get your exe from there and then trigger the install.
ok
Hi Oliver,
Thank you.
No worries, I’ve already implemented the script successfully. Actually, I’ve discovered your site when I was searching for Azure Management Extension troubleshooting.
Useful post, thanks!
There is a simpler method to write to 64-bit registry from 32-bit process using .NET.
Example:
# Set Office Default File Types to Office Open XML formats
$dotNetKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::CurrentUser, [Microsoft.Win32.RegistryView]::Registry64)
$dotNetKey.CreateSubKey(‘Software\Policies\Microsoft\office\16.0\common\general’).SetValue(‘ShownFileFmtPrompt’, 1, [Microsoft.Win32.RegistryValueKind]::DWord)
Oh yes I totally agree with you! Thanks for sharing the example!
I have chosen the restart method as it is more universal and the registry entry is just one use case I thought is easy to demonstrate. You can even use it for command-lets only available in x64 PowerShell.
I created a PS template here to have a kind of starting point: https://gist.github.com/okieselbach/4f11ba37a6848e08e8f82d9f2ffff516
But again many thanks for sharing the example it will certainly help others if they are just looking for writing 64-bit registry!
best,
ok
Hi Olivier, when I assign a scsript to a group of users, and a new member is added to the group, the script is not applied to the new user. Is this by design? Is there a way to work around this ?
Hi Jan,
it should be assigned and the new member should see and execute the PS script. In my environment I do this from time to time and it is working. So what you see is not by design. You need to troubleshoot the device why it is not receiving the script.
best,
Oliver
I am unclear. If I can push a desktop installer through the CSP, what is the advantage of using the powershell, just the http pull part? What are the benefits and drawbacks of either approach?
Hi William,
okay I try to answer this is my point of view :-). Yes you can push a Desktop Installer via CSP but with certain limitations:
– max 2 GB in size
– single MSI file (no additional files just a single .msi)
– no user feedback
– no forward slash / parameters only MSI Properties as parameters
– even no .exe files like setup.exe
– no installs in defined sequence, random install if more than one install is assigned
Advantage:
– you can use the company portal as app store
With the PowerShell approach you get more control and possibilities. Its basically a wrapper to kick of the install process. Within that wrapper you can basically do anything. Compare it with a ConfigMgr package. Within our wrapper PS script we can check pre-requisites download large install files, no matter which content to load, prepare or clean something before kicking off the installer, you can build your own retry or failure handling, use a well proven framework like Chocolatey for app installs, install apps in a defined sequence, …
drawback: no company portal can be used, no user feedback by default but possible to script something.
best,
Oliver
What if you would like to edit the registry and not add to it.
I tried replacing the New-RegistryKey line with:
Set-ItemProperty -Path “HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced” -Name “HideFileExt” -Value “0”
but that did not work.
Hi,
try to enforce writing to x64 path on x64 bit systems and run the script in user context (use my PS template).
Set “run the script using the logged on credentials” to YES as we want to write to user hive in registry.
I added a script sample (HideFileExt.ps1) which is working for me to my GitHub repository:
https://github.com/okieselbach/Intune/tree/master/ManagementExtension-Samples
best,
Oliver
Worked like a charm, thanks so much!
If you need to force the script to run, delete the regkey “result” under HKLM\SOFTWARE\Microsoft\IntuneManagementExtension\Policies\UserGUID\ScriptGUID
Hi Tiago,
that’s true and always good to mention. I already described that on my follow up post Part 2: https://oliverkieselbach.com/2018/02/12/part-2-deep-dive-microsoft-intune-management-extension-powershell-scripts/ in section: Script execution retry interval when failed?
best,
Oliver
Thanks, didn’t saw the second part 🙂
Hi Oliver,
I truly need your help! Appreciate, any information that you provide.
1. I have a PowerShell script which needs to be deployed only to newly enrolled devices in intune
2. I have another PowerShell which needs to be deployed to the non-encrypted device. the script needs to send a message box to users if their device is not encrypted. the issue I am facing is, BitLocker status needs admin access to execute whereas displaying form needs no admin access. I am stuck 😦
****
$BLinfo = Get-Bitlockervolume
if($blinfo.ProtectionStatus -eq ‘off’ -and $blinfo.EncryptionPercentage -ne ‘100’)
{
function msgPopUp($msg){
#Create Default Document Form
Add-Type -AssemblyName System.Windows.Forms
$msgForm = New-Object Windows.Forms.Form
$msgForm.Size = New-Object Drawing.Size @(1800,270)
$msgForm.StartPosition = “CenterScreen”
$msgForm.ShowIcon = $False
$msgForm.FormBorderStyle = “FixedDialog”
$msgForm.Text = “IT Information”
$msgForm.MinimizeBox = $False
$msgForm.MaximizeBox = $False
$msgForm.WindowState = “Normal”
$msgForm.BackColor = “Blue”
$msgForm.ForeColor = “White”
#Write Message on msgForm Box
$msgLabel = New-Object System.Windows.Forms.Label
$msgLabel.Location = New-Object System.Drawing.Size(10,10)
$msgLabel.Size = New-Object System.Drawing.Size(400,300)
$msgLabel.Font = New-Object System.Drawing.Font(“Arial”,11,[System.Drawing.FontStyle]::Bold)
$msgLabel.Text = $msg
$msgLabel.AutoSize = $True
$msgForm.Controls.Add($msgLabel)
#Display msgForm
$msgForm.ShowDialog()
}
$msgText = “Your device is not encrypted”
msgPopUp($msgText);
***
Hi PriyaGop,
I‘m on vacation with limited access to my normal work environment, but I want to give you a short hint how to solve such a problem. You have to use a tool like ServiceUI.exe from the Microsoft Deployment Toolkit (MDT) to run a process in the user context from system context. This problem is well known in the ConfigMgr world and solved by tools like ServiceUI.exe. Basically you run ServiceUI.exe and it copies a process context of a already running process to run your new process. For example you can copy explorer.exe (user context processs) and start your script to display your message box. With this you are able to run a User process from system context.
Regarding your first issue you can write a regkey as marker to check for. If the script will run again it will check the marker and won‘t execute (simple if).
I‘ll hope this gives a hint how to solve your issue. Otherwise get in touch with me by the end of next week. I can assist to find a working solution then.
Best,
Oliver
Oliver , awesome work! please keep up!
I just have one question , how can you bypass the execution policy on the machines , or change it remotely with intune?
Hi,
first of all thank you very much.
Regarding your question you have multiple options:
– Running a script with the Intune Management Extension doesn’t require any change to the execution policy. The Intune Management Extension takes care of correct execution of your scripts.
– If you like to run a script and this should bypass the execution policy you can simply execute powershell.exe -ExecutionPolicy ByPass -File “full-script-path\script.ps1”
– Changing the Execution Policy on devices via Intune Management Extension by calling Set-ExecutionPolicy is not working as the cmdlet is not able to modify the MachinePolicy. MachinePolicy for Execution Policy is allowed to modify by Group Policy only.
– Another way would be to connect to the remote devices via Invoke-Command but this is more a server management scenario not that ideal for clients traveling anywhere
Hope this helps and provides some ideas.
best,
Oliver
Already did , thanks 🙂 it`s working.
Learn a lot this night with your blog .
Hi Oliver!
I do have one script thats not able to bypass execution policy via intune management extension. All other scripts are able to run except this one and Im not sure how to go about this. Any advice?
Daniel
Hey Motto,
I think without the IME log file and the script itself it is hard to give any advice why it may fail…
best,
Oliver
Hi,
2 questions hope you can and will answer.
1) can we deploy PS script to computers?
2) if we deploy script1 to userA and he logs in on computer1 the script1 will run. But what happens if userA logs in o computer2 will the script also be runned on computer2 ?
Hi RKast,
you’re welcome! Here we go 🙂
1) no it’s currently not possible to deploy PS scripts to computer groups only user groups.
2) Yes the script is assigned to a user and it will run on every device the user logs on. In your example script1 will run on computer2.
best,
Oliver
Tips my hat of to you sir.
Thanks once again.
Funny thing is on technet i read deploying PS scripts to computer is not possible, but on dozens of blogs they assign it to computer group 🙂
Thanks for the update. The start in 64 bit mode is very welcome addition!