Part 2, Deep dive Microsoft Intune Management Extension – PowerShell Scripts

Because of the popularity of my first blog post Deep dive Microsoft Intune Management Extension – PowerShell Scripts, I’ve decided to write a second post regarding Intune Management Extension to further explain some architecture behind this feature and upcoming question from the community. A deeper understanding helps to successful troubleshoot the feature.

Table of content for easy navigation

 

Details of the MSI deployment of the Intune Management Extension agent?

Please read the first article Deep dive Microsoft Intune Management Extension – PowerShell Scripts, to get an understanding of the MSI install job.

The EnterpriseDesktopAppManagement CSP takes care of this task and delivers the MSI to the device and starts the execution. The CSP provides some helpful information in registry for troubleshooting.

In the registry at HKLM\SOFTWARE\Microsoft\EnterpriseDesktopAppManagement\<SID>\<MSI-ProductCode> you can find helpful information for troubleshooting:

EnterpriseDesktopAppManagementCspRegistry

Highlighted value names are described in detail here:

CurrentDownloadUrl: URL to the MSI install file.

EnforcementRetryCount: The number of times the download and installation operation will be retried before the installation will be marked as failed.

EnforcementRetryIndex: The current number of retry.

EnforcementRetryInterval: Amount of time, in minutes between retry operations.

EnforcementStartTime: Start time of enforcement.

EnforcementTimeout: Amount of time, in minutes that the installation process can run before the installer considers the installation may have failed and no longer monitors the installation operation.

LastError: Error after last execution.

Status: The Status can have the following values according to the EnterpriseDesktopAppManagement CSP documentation:

Value = Status
10 = Initialized
20 = Download In Progress
25 = Pending Download Retry
30 = Download Failed
40 = Download Completed
48 = Pending User Session
50 = Enforcement In Progress
55= Pending Enforcement Retry
60 = Enforcement Failed
70 = Enforcement Completed

In case of no sidecar agent on the device the status may indicate an error or it is still in progress of downloading. Try to figure out this status. Correlate the status with your device and environment. There may be a proxy issue?

According to the screenshot above the CSP will try it 3 times with a timeout of 10 min and then it will be marked as failed. Is there a retry after x days, I assume yes but I don’t have some documentation for it. As soon as I have more information I will update the article accordingly.

 

Be aware of the log files regarding sensitive data!

The following script below will create a local user account with a specified static password. It is a working script, but be aware that it should not be used in production! The whole script content will be logged to the IntuneManagementExtension.log file. There you will find the clear text static password as defined in the script. As an example I have copied and marked the particular log file entries below the script to show that. Disk encryption technologies like BitLocker are lowering the risks a bit, but even then this is not a concept to be used in production environments! An attacker can read the log files with standard user permissions and get the sensitive data. Without BitLocker encryption this can be done even offline!

Again: I do NOT recommend transferring sensitive data like passwords via this approach! I know it’s often done. There is a similar approach for on-prem AD which is often used. The GPO preferences for local user management tasks uses a simple xml file with passwords 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 from Microsoft.

# Author: Oliver Kieselbach
# Date: 01/03/2018
# Description: Create a local user account.

# REMARK: DO NOT USE IN PRODUCTION, Password will not be unique and be visible in clear text in log file!

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

if (!$Is64Bit) { Restart-As64BitProcess }
else
{
Start-Transcript -Path "$env:temp\CreateLocalUser.log"

$password = ConvertTo-SecureString "SecretP@ssw0rd!" -AsPlainText -Force
New-LocalUser "John" -AccountNeverExpires:$true -FullName "John Doe" -Password $password

Stop-Transcript
}

To demonstrate the problem with this approach, here is the corresponding log file part after script execution with the complete script and static password in clear text:

sensitivedatainlogfile.png

 

Getting content of scripts once they are uploaded to Intune?

The Intune Azure Portal does not provide any UI element to show the uploaded script again. Thanks to the GitHub repository Intune PowerShell Samples we do not script something by our own. We can use a script provided from there called DeviceManagementScripts_Get.ps1. It will get all uploaded scripts, including script content, and details from the Intune API via Microsoft Graph API.

Get it from here:
https://github.com/microsoftgraph/powershell-intune-samples/blob/master/DeviceConfiguration/DeviceManagementScripts_Get.ps1

Save it locally and run it with PowerShell and provide your Intune Administrator credentials when asked for:

IntuneManagementExtensionScriptContent

 

What about return codes (exit codes) of PowerShell scripts?

At the moment the Intune Management Extension will gather various results, but the Intune Azure portal does not show them in an UI element (if it will change in the future and we have something available, I will update the post accordingly). As for now scripts can be executed through the Intune agent and the Intune UI will show just the execution state success or failure. The status is related to successful agent execution, like no hash mismatch, no script content problem, and no error output… it does not reflect the script exit code.

Scripts like this, I called it “failscript.ps1” are handled as success at the moment:

[Environment]::Exit(-1)

See here the results in Intune Azure Portal:

IntuneAgentFailscriptStatus

Perhaps Intune will show us script exit codes in the UI sometime, then we could verify this easily with the failscript from above.

If we use Write-Error cmdlet in our scripts then Intune will pick up the error output. Here an example:

Write-Error -Message "Could not write regsitry value" -Category OperationStopped

This gives us a failed status in the monitoring section of the PowerShell scripts:

IntuneAgentWriteErrorScriptStatus

 

 

Where can I find a helpful PowerShell script template to start with?

I have made a simple script template which:

  1. Enforces execution in x64 PowerShell (restart of PowerShell as x64 process)
  2. Generates a log file in C:\Windows\temp when run in system context
    or %LocalAppData%\temp when run in user context
  3. Has exit code handling (exit code is even gathered when restarted as x64 process)
  4. Uses Write-Error to signal Intune a failed execution and makes sure we have reporting reflecting our script execution success (standard error is even gathered when restarted as x64 process)

For ongoing maintenance it is provided via GitHub Gist:

 

Script execution retry interval when failed?

The PowerShell scripts are executed via agent on the target device. If an execution fails, the agent tries to run the script again during next check-in. Current check-in interval is every 60 minutes. This procedure is limited to 3 attempts!

This is tracked in registry at:

HKLM\SOFTWARE\Microsoft\IntuneManagementExtension\Policies\UserGUID\ScriptGUID

The DownloadCount also means execution count and the result is tracked as Success or Failed.

IntuneManagementExtensionScriptRegistry

If we change the run as account (user/system), signature check or script content, the DownloadCount will be reset, and the agent will try another 3 attempts to execute the script.

If a script should be enforced to run again, we can simply reset DownloadCount and ErrorCode to 0 and set Result and ResultDetails to nothing (empty string). After this we just restart the Microsoft Intune Management Extension Service (IntuneManagementExtension) and the script will rerun again on this device.

 

Can we schedule scripts?

No we do not have a run schedule at the moment. For example the agent does not execute the script every x hours. The agent checks every 60 minutes for new policies in the backend – during this no re-run of scripts occurs once a script is successful executed. If scheduling of scripts is needed I suggest to register the script as a scheduled tasks via PS commands. That’s the only way at the moment (as soon as we have something available for scheduling I will update the post accordingly).

Below a script to copy the scheduled script to a certain folder like C:\ProgramData\CustomScripts\myScript.ps1 and then register a scheduled task to run it periodically:

# Author: Oliver Kieselbach
# Date: 01/31/2018
# Description: install ps script and register scheduled task

# The script is provided "AS IS" with no warranties.

# define your PS script here
$content = @"
Out-File -FilePath "C:\Windows\Temp\test.txt" -Encoding unicode -Force -InputObject "Hello World!"
"@

# create custom folder and write PS script
$path = $(Join-Path $env:ProgramData CustomScripts)
if (!(Test-Path $path))
{
 New-Item -Path $path -ItemType Directory -Force -Confirm:$false
}
Out-File -FilePath $(Join-Path $env:ProgramData CustomScripts\myScript.ps1) -Encoding unicode -Force -InputObject $content -Confirm:$false

# register script as scheduled task
$Time = New-ScheduledTaskTrigger -At 12:00 -Daily
$User = "SYSTEM"
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ex bypass -file `"C:\ProgramData\CustomScripts\myScript.ps1`""
Register-ScheduledTask -TaskName "RunCustomScriptDaily" -Trigger $Time -User $User -Action $Action -Force

Here 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 how to 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.

 

Integrated HealthCheck of Intune Management Extension agent?

The Intune Management Extension creates a scheduled task which will run a Health Check once a day.

IntuneManagementExtensionHealthCheck

If familiar with ConfigMgr and the ConfigMgr agent, there we have the same concept. The health check involves 4 files:

ClientHealthEval.exe and ClientHealthEval.exe.config: The binary which runs the health check.

HealthCheck.xml: The xml with all rules to run to perform the health check.

HealthReport.json: The json report with results of the rules defined by the xml.

The tests are defined in the xml file to check the agent registered service, startup type, service status and memory usage.

 

User interaction and timeout?

When using a scripts with the typical user interaction (like shown as an example in my first article Deep dive Microsoft Intune Management Extension – PowerShell Scripts), be advised that this script is getting executed and showing a dialog which blocks the script from finishing, until the user interacts with the dialog and then let the script finish. This can lead to timeouts and then scripts are getting marked as failed even when successfully ran.

The current default script timeout is 10 minutes for execution which is currently not adjustable.

 

PowerShell scripts execution without user logon?

Since 22th of October the end users are no longer required to be logged in on the device to execute PowerShell scripts.

 

If you find something missing or changed feel free to leave a comment. Thanks!

31 thoughts on “Part 2, Deep dive Microsoft Intune Management Extension – PowerShell Scripts”

  1. Do i understand it correct that the PS script that is send with Intune, reads the content of the txt file @ the location $content and creates a PS1 script from the content? Very nice idea and blogpost. We could also combine this with Azure File Storage for storing and retreving files.

    1. Yes the script itself is defined in the content variable and then written to disk. I thought it’s nice to demonstrate such an approach. Combination with Azure Blob storage or File storage would certainly be possible. For this simple task it would require more involved services which must be configured upfront, but for different cases certainly useful.

  2. Oliver, fantastic work, thanks!
    One addition if someone is trying to get the Intune PowerShell script exit codes and the outputs of the script, these are available in the Microsoft Graph.
    If you visit https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/ you will get the list of the PowerShell scripts in Intune with their properties.
    By selecting the “id” of your script, you can deep dive into execution data.
    Under deviceRunStates you get access to all the relevant information:
    eg. https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/b6759ffb-063c-4f96-8199-bd30d0e41467/deviceRunStates
    You get a list of entries like this one:
    {
    “id”: “–deleted–“,
    “runState”: “success”,
    “resultMessage”: “\r\n”,
    “lastStateUpdateDateTime”: “2018-03-30T21:14:13Z”,
    “errorCode”: 0,
    “errorDescription”: null
    },

    It is easy to combine with the Intune DWH in Power BI.
    Have fun with it!

    1. Thank you Márton!
      And yes this is definitely a way to get to some good reporting. I would love to see this in the native Intune Azure portal but we will see what the future brings us. The Intune service gathers the information quite a while but does not expose them in the UI. I guess for the time being the only way is to build something our own. Very good that you mentioned it here.

  3. This didnt work for me 😦 so I can see that the script is been created and the files are downloading from my blob, however, the task is not been scheduled :S

    The only thing I have changed is

    $Action = New-ScheduledTaskAction -Execute “C:\ProgramData\CustomScripts\CS_Agent_Deploy.bat”]

    The scripts works create when it runs directly on the machine but when I try and deploy it through intune it simply will not create the task…?

    Does anyone have any ideas?

    1. Hi Nick,

      I verified my sample script again and it’s still working (executed it on 2 AAD/Intune devices successfully). Did you configured my sample script and is this working in your environment? I’m not seeing a failure in your command line. Maybe try to use $Action = New-ScheduledTaskAction -Execute
      “cmd.exe” -Argumente “C:\ProgramData\CustomScripts\CS_Agent_Deploy.bat”

      best,
      Oliver

      1. Not sure if your reply has a typo, but i guess -Argumente should be -Argument.

        Also a question, that 10 min script timeout is only for user dialogs, or for every script that is running (I have some issues with Illustrator deployment, that says it failed but work perfect on the machine).

  4. Hi Oliver,

    I have some troubles with large files while using the EXE deployment method.
    Probably even the download takes 10+ minutes.

    Do you have any tips orso for the 10min timeout?
    I guess I am not the only one with this question 🙂

    1. Honestly not at the moment. Sure it’s possible to write an wrapper to execute something on the client and return a success result code to Intune but then you would loose proper reporting. It would be possible to report a result code to a azure function or azure storage or log analytics and then build a dashboard to evaluate these results… but imho this is not very ideal. I think MS has to deliver something here in future. If you like to discuss about other options beside Intune maybe drop me an email.

      best,
      Oliver

      1. I am currenlty checking with a hash check in the beginning of the script. If its not the same, it will download the ZIP and will exit with exit code 1.
        Else it will extract the zip, and do all the other commands and clean up tasks.

        Right now I can see some green status messages for some machines and adding some more machines to be sure it wasnt luck. I do not like this solution, but right now I think it is the only way.

  5. if you have a variable as part of the script which writes to file it does not write the variable for some reason. Any ideas?

  6. Hi Olivier,

    The Intune Management Extension won’t get installed on multiple test clients of me. The registry values/folders you are talking about aren’t even created. Is there something else I can check or is it possible to install the extension manually?
    All the requirements are ok.

    – Test device: Windows 10 1803
    – EMS E3 license
    – Auto Enrollment enabled
    – Azure AD Joined

    All other policies are applied as expected.

    Thank you

    1. Hi,

      One question because you didn’t mentioned this. Did you actually assigned a PowerShell script to a user? The agent itself can be installed on-the-fly by Microsoft. So it might be that your machines you are investigating the problem don’t have received a trigger to do the agent install. As soon as a script is assigned the agent will checked and if needed updated or even installed if not available.

      best,
      Oliver

  7. Hi Oliver

    Great article, thank you.
    I have a similar issue to InTuneDude above. I have assigned a Powershell script to some test devices in InTune. The InTune Management Extension gets installed, but the scripts don’t execute.
    The registry key HKLM\SOFTWARE\Microsoft\IntuneManagementExtension exists, but the subkey ‘Policies’ does not. Also, the subfolder ‘Policies’ does not exist in C:\Program Files (x86)\Microsoft Intune Managment Extension.
    Other policies from InTune are applying successfully (autopilot, app installations, device sonfiguration). Any pointers?

    Thank you
    Nick

    1. Hi Nick,
      thanks. First of all are you assigning the PowerShell scripts to users? This is necessary. The policies key is only available as soon as you got the first PowerShell script on your device. Please verify with user assignment.

      best,
      Oliver

      1. Hi Oliver
        The script are assigned to devices – I’ll try assigning to users instead.
        Thanks

  8. Hi Oliver,

    Great article! It’s a shame that we can’t provide a secure/encrypted object with PowerShell scripts in Intune, for preventing sensitive data from being exposed.

    I wrote an article about an alternative solution for creating Local Administrator accounts and storing their passwords using Intune PowerShell scripts, Azure Functions and Azure Key Vault.

    I would be pleased if you would take a look at https://www.srdn.io/2018/09/serverless-laps-powered-by-microsoft-intune-azure-functions-and-azure-key-vault/

    Looking forward to hear your thoughts!

    1. Hi John,

      nice work and I’m glad that you openly communicate the pitfalls of the solution. So everyone gets a clear picture and can rate the pitfalls and the benefits by their own.
      Something which comes to my mind is, that a device can be renamed by a user easily with the company portal for example. As soon as this happens it might be difficult to identify the device when you need the local admin password.
      I’m curious in the scenarios you are using the local account. We have several thousands of computers migrated now with the modern management approach and Intune etc. and we can life without a local admin account. In the beginnings I thought this is a must have feature, but by now it’s not that important for us anymore. Can you tell me about your scenarios where you are using or depended on a local admin account?

      best,
      Oliver

      1. We need a local admin account so that service desk users can remote using logmein to do admin tasks without being global administrators of Azure.

        I was able to leverage custom Custom OMA-URI Settings in device configuration in Intune to create an account and assign a password however i cant change the password if I need to.

        What i tried to do is run a powershell script from intune to delete the account but im getting a “the term remove-localuser is not recognized”. Which is weird because I can run that command locally.

        wondering if you can assist?

      2. Hi Adeel,

        first of all you don’t need to be global administrator to remote logon, you can add additional administrators to your Win10 AADJ devices by navigating to portal.azure.com > Azure Active Directory > Devices > Device Settings > additional local administrators on Azure AD joined devices > click selected > add your accounts. Global Administrators should be a very low number and service desk should only get the rights to the devices not global admin permissions.
        Regarding the localuser PS cmdlets 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 from GitHub https://github.com/okieselbach/Intune/blob/master/ManagementExtension-Samples/IntunePSTemplate.ps1. This will re-launch the process in x64 and the cmdlet should succeed.

        best,
        Oliver

  9. Using Intune I already have a PS script called Invoke-MSIntuneDriverUpdate.ps1 that we are using to update drivers on the machines. I am wanting to get that script to a schedule task to run monthly. Will your install ps script and reguster scheduled task work for that? We are using only Intune, no SCCM or any other piece of software.

    Thanks!

    1. Hi Brent,

      I guess it’s the script from my SCConfigMgr colleague Maurice and this script is about 30KB in size. So you could embed this into my demo script but you could also take his script and build a .intunewin package to deploy it. That would be my preferred way of doing it. You could put the driver script into the source folder and another installer script which copies it over to the device and registers the task. The nice thing about this is, you get also uninstall capabilities. So put you installer PS script and Maurice script into a Win32 app package, build the .intunewin and call in the command line powershell -ex bypass -file yourinstaller.ps1 to get it working.

      best,
      Oliver

      1. When it asks me to specify the setup file it wont let me put the PS Script and Maurice Script at the same time. I can build each out individually as .intunewin but wont let me select both.

      2. You don’t need to specify both scripts during .intunewin creation, put both scripts in the source folder and just specify your newly created installer.ps1 and then when built go into the Intune portal and use the command line: powershell.exe -ex bypass -file installer.ps1.
        In your installer.ps1 make sure to copy the Invoke-MSIntuneDriverUpdate.ps1 script somewhere into the file system (e.g. C:\ProgramData\MyDriverScript\Invoke-MSIntuneDriverUpdate.ps1) and register the scheduled task and specify the UpdateDriver script in the scheduled task with the file path on the disk like in the example: C:\ProgramData\MyDriverScript\Invoke-MSIntuneDriverUpdate.ps1… I think you get the idea right?

        best,
        Oliver

  10. Using $process = Start-Process -PassThru causes the command to return a process object that contains the StandardInput, StandardErrors and StandardOutput properties. Great for catching events that happened in the x64 powershell instance.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s