Microsoft Defender has security recommendations for the “Fix unquoted service path for Windows services.” (CVE-2013-1609, CVE-2014-0759, CVE-2014-5455) These might be reported for things like Dell services, and you can fix them manually by editing the Registry on the affected device. But… There is an easier way 🙂 This blog post will show you how to create a Proactive Remediation in Intune for it.
How does it work?
Intune’s Proactive Remediation detection script will check for services with a Pathname not enclosed in quotes. If it finds one, it will exit the detection script with an Error Code of 1 and write an error message in the Intune Management Extension log.
If the detection is exited with that Error Code of 1 (And not with an Error Code of 0 telling that no issue was found), then the Remediation script will start. I found an excellent PowerShell script for this issue from Vector BCO ( https://github.com/VectorBCO/windows-path-enumerate) that will fix the problem by adjusting the Pathname in the Registry (The ImagPath string)
Note: Proactive Remediations are only available for customers with the following licenses:
– 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
The Scrips
Detection.ps1
The PowerShell script for detecting services without quotes:
# Detection if (Get-CIMInstance -Class Win32_Service | Select-Object Name, PathName, DisplayName, StartMode | Where-Object { $_.StartMode -eq 'Auto' -and $_.PathName -notmatch 'Windows' -and $_.PathName -notmatch '"' }) { Write-Host ("Service found without quotes") exit 1 } else { Write-Host ("No Service found without quotes") exit }
Remediation.ps1
The PowerShell script for fixing the services without quotes (From Vector BCO):
<# .SYNOPSIS Fix for Microsoft Windows Unquoted Service Path Enumeration .DESCRIPTION Script for fixing vulnerability "Unquoted Service Path Enumeration" in Services and Uninstall strings. Script modifying registry values. Require Administrator rights and should be run on x64 powershell version in case if OS also have x64 architecture .PARAMETER FixServices This bool parameter allow proceed Services with vulnerability. By default this parameter enabled. For disabling this parameter use -FixServices:$False .PARAMETER FixUninstall Parameter allow find and fix vulnerability in UninstallPaths. Will be covered paths for x86 and x64 applications on x64 systems. .PARAMETER FixEnv Find services with Environment variables in the ImagePath parameter, and replace Env. variable to the it value EX. %ProgramFiles%\service.exe will be replace to "C:\Program Files\service.exe" .PARAMETER WhatIf Parameter should be used for checking possible system impact. With this parameter script would not change anything on your system, and only will show information about possible (needed) changes. .PARAMETER CreateBackup When switch parameter enabled script will export registry tree`s specified for services or uninstall strings based on operator selection. Tree would be exported before any changes. [Note] For restoring backup could be used RestoreBackup parameter [Note] For providing full backup path could be used BackupName parameter .PARAMETER RestoreBackup This parameter will allow restore previously created backup. If BackupName parameter would not be provided will be used last created backup, in other case script will try to find selected backup name [Note] For creation backup could be used CreateBackup parameter [Note] For providing full backup path could be used BackupName parameter .PARAMETER BackupFolderPath Parameter would be proceeded only with CreateBackup or RestoreBackup If CreateBackup or RestoreBackup parameter will be provided, then path from this parameter will be used. During backup will be created reg file with original values per each service and application that will be modified During restoration all reg files in the specified format will be iterable imported to the registry Input example: C:\Backup\ Backup file format: for -FixServices switch => Service_<ServiceName>_YYYY-MM-DD_HHmmss.reg for -FixUninstall switch => Software_<ApplicationName>_YYYY-MM-DD_HHmmss.reg .PARAMETER Passthru With this parameter will be returned object array without any messages in a console Each element will continue Service\Program Name, Path, Type <Service\Software>, ParamName <ImagePath\UninstallString>, OriginalValue, ExpectedValue .PARAMETER Silent [i] Silent parameter will work only together with Passthru parameter If at least 1 Service Path or Uninstall String should be fixed script will return $true Otherwise script will return $false Example: .\windows_path_enumerate.ps1 -FixUninstall -WhatIf -Passthru -Silent Output: $true Description: $true mean at least 1 service need to be fixed. WhatIf switch mean that nothing was fixed, registry was only diagnosed for the vulnerability .PARAMETER Help Will display this help message .PARAMETER LogName Parameter allow to change output file location, or disable logging setting this parameter to empty string or $null. .EXAMPLE # Run powershell as administrator and type path to this script. In case if it will not run type dot (.) before path. . C:\Scripts\Windows_Path_Enumerate.ps1 VERBOSE: -------- 2017-02-19 15:43:50Z : INFO : ComputerName: W8-NB 2017-02-19 15:43:50Z : Old Value : Service: 'BadDriver' - %ProgramFiles%\bad driver\driver.exe -k -l 'oper' 2017-02-19 15:43:50Z : Expected : Service: 'BadDriver' - "%ProgramFiles%\bad driver\driver.exe" -k -l 'oper' 2017-02-19 15:43:50Z : SUCCESS : New Value of ImagePath was changed for service 'BadDriver' 2017-02-19 15:43:50Z : Old Value : Service: 'NotAVirus' - C:\Program Files\Strange Software\virus.exe -silent 2017-02-19 15:43:51Z : Expected : Service: 'NotAVirus' - "C:\Program Files\Strange Software\virus.exe" -silent' 2017-02-19 15:43:51Z : SUCCESS : New Value of ImagePath was changed for service 'NotAVirus' Description ----------- Fix 2 services 'BadDriver', 'NotAVirus'. Env variable %ProgramFiles% did not changed to full path in service 'BadDriver' .EXAMPLE # This command, or similar could be used for running script from SCCM Powershell -ExecutionPolicy bypass -command ". C:\Scripts\Windows_Path_Enumerate.ps1 -FixEnv" VERBOSE: -------- 2017-02-19 15:43:50Z : INFO : ComputerName: W8-NB 2017-02-19 15:43:50Z : Old Value : Service: 'BadDriver' - %ProgramFiles%\bad driver\driver.exe -k -l 'oper' 2017-02-19 15:43:50Z : Expected : Service: 'BadDriver' - "C:\Program Files\bad driver\driver.exe" -k -l 'oper' 2017-02-19 15:43:50Z : SUCCESS : New Value of ImagePath was changed for service 'BadDriver' 2017-02-19 15:43:50Z : Old Value : Service: 'NotAVirus' - %SystemDrive%\Strange Software\virus.exe -silent 2017-02-19 15:43:51Z : Expected : Service: 'NotAVirus' - "C:\Strange Software\virus.exe" -silent' 2017-02-19 15:43:51Z : SUCCESS : New Value of ImagePath was changed for service 'NotAVirus' Description ----------- Fix 2 services 'BadDriver', 'NotAVirus'. Env variable %ProgramFiles% replaced to full path 'C:\Program Files' in service 'BadDriver' .EXAMPLE # This command, or similar could be used for running script from SCCM Powershell -ExecutionPolicy bypass -command ". C:\Scripts\Windows_Path_Enumerate.ps1 -FixUninstall -FixServices:$False -WhatIf" VERBOSE: -------- 2018-07-02 22:23:02Z : INFO : ComputerName: test 2018-07-02 22:23:04Z : Old Value : Software : 'FakeSoft32' - c:\Program files (x86)\Fake inc\Pseudo Software\uninstall.exe -silent 2018-07-02 22:23:04Z : Expected : Software : 'FakeSoft32' - "c:\Program files (x86)\Fake inc\Pseudo Software\uninstall.exe" -silent Description ----------- Script will find and displayed .EXAMPLE # This command will return $true if at least 1 path should be fixed or $false if there nothing to fix # Log will not be available .\windows_path_enumerate.ps1 -FixUninstall -WhatIf -Passthru -Silent -LogName '' VERBOSE: -------- true .NOTES Name: Windows_Path_Enumerate.PS1 Version: 3.5.1 Author: Vector BCO Updated: 8 April 2021 .LINK https://github.com/VectorBCO/windows-path-enumerate/ https://gallery.technet.microsoft.com/scriptcenter/Windows-Unquoted-Service-190f0341 https://www.tenable.com/sc-report-templates/microsoft-windows-unquoted-service-path-enumeration http://www.commonexploits.com/unquoted-service-paths/ #> [CmdletBinding(DefaultParameterSetName = "Fixing")] Param ( [parameter(Mandatory = $false, ParameterSetName = "Fixing")] [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [Alias("s")] [Bool]$FixServices = $true, [parameter(Mandatory = $false, ParameterSetName = "Fixing")] [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [Alias("u")] [Switch]$FixUninstall, [parameter(Mandatory = $false, ParameterSetName = "Fixing")] [Alias("e")] [Switch]$FixEnv, [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [Alias("cb", "backup")] [switch]$CreateBackup, [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [Alias("rb", "restore")] [switch]$RestoreBackup, [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [string]$BackupFolderPath = "C:\Temp\PathEnumerationBackup", [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [string]$LogName = "C:\Temp\ServicesFix-3.5.1.Log", [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [parameter(Mandatory = $False, ParameterSetName = "Restoring")] [Alias("ShowOnly")] [Switch]$WhatIf, [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [Switch]$Passthru, [parameter(Mandatory = $False, ParameterSetName = "Fixing")] [Switch]$Silent, [parameter(Mandatory = $true, ParameterSetName = "Help")] [Alias("h")] [switch]$Help ) Function Fix-ServicePath { <# .SYNOPSIS Microsoft Windows Unquoted Service Path Enumeration .DESCRIPTION Use Fix-ServicePath to fix vulnerability "Unquoted Service Path Enumeration". .PARAMETER FixServices This switch parameter allow proceed Services with vulnerability. By default this parameter enabled. For disable this parameter use -FixServices:$False .PARAMETER FixUninstall Parameter allow find and fix vulnerability in UninstallPath. Will be covered paths for x86 and x64 applications on x64 systems. .PARAMETER FixEnv Find services with Environment variables in the ImagePath parameter, and replace Env. variable to the it value EX. %ProgramFiles%\service.exe will be replace to "C:\Program Files\service.exe" .PARAMETER WhatIf Parameter should be used for checking possible system impact. With this parameter script would not be changing anything on your system, and only will show information about possible changes .EXAMPLE Fix-ServicePath VERBOSE: -------- 2017-02-19 15:43:50Z : Old Value : Service: 'BadDriver' - %ProgramFiles%\bad driver\driver.exe -k -l 'oper' 2017-02-19 15:43:50Z : Expected : Service: 'BadDriver' - "%ProgramFiles%\bad driver\driver.exe" -k -l 'oper' 2017-02-19 15:43:50Z : SUCCESS : New Value of ImagePath was changed for service 'BadDriver' 2017-02-19 15:43:50Z : Old Value : Service: 'NotAVirus' - C:\Program Files\Strange Software\virus.exe -silent 2017-02-19 15:43:51Z : Expected : Service: 'NotAVirus' - "C:\Program Files\Strange Software\virus.exe" -silent' 2017-02-19 15:43:51Z : SUCCESS : New Value of ImagePath was changed for service 'NotAVirus' Description ----------- Fix 2 services 'BadDriver', 'NotAVirus'. Env variable %ProgramFiles% did not changed to full path in service 'BadDriver' .EXAMPLE Fix-ServicePath -FixEnv VERBOSE: -------- 2017-02-19 15:43:50Z : Old Value : Service: 'BadDriver' - %ProgramFiles%\bad driver\driver.exe -k -l 'oper' 2017-02-19 15:43:50Z : Expected : Service: 'BadDriver' - "C:\Program Files\bad driver\driver.exe" -k -l 'oper' 2017-02-19 15:43:50Z : SUCCESS : New Value of ImagePath was changed for service 'BadDriver' 2017-02-19 15:43:50Z : Old Value : Service: 'NotAVirus' - %SystemDrive%\Strange Software\virus.exe -silent 2017-02-19 15:43:51Z : Expected : Service: 'NotAVirus' - "C:\Strange Software\virus.exe" -silent' 2017-02-19 15:43:51Z : SUCCESS : New Value of ImagePath was changed for service 'NotAVirus' Description ----------- Fix 2 services 'BadDriver', 'NotAVirus'. Env variable %ProgramFiles% replaced to full path 'C:\Program Files' in service 'BadDriver' .EXAMPLE Fix-ServicePath -FixUninstall -FixServices:$False -WhatIf VERBOSE: -------- 2018-07-02 22:23:04Z : Old Value : Software : 'FakeSoft32' - c:\Program files (x86)\Fake inc\Pseudo Software\uninstall.exe -silent 2018-07-02 22:23:04Z : Expected : Software : 'FakeSoft32' - "c:\Program files (x86)\Fake inc\Pseudo Software\uninstall.exe" -silent Description ----------- Script will find problems and only display result but will not change anything .NOTES Name: Fix-ServicePath Version: 3.5 Author: Vector BCO Last Modified: 3 May 2020 .LINK https://gallery.technet.microsoft.com/scriptcenter/Windows-Unquoted-Service-190f0341 https://www.tenable.com/sc-report-templates/microsoft-windows-unquoted-service-path-enumeration http://www.commonexploits.com/unquoted-service-paths/ #> Param ( [bool]$FixServices = $true, [Switch]$FixUninstall, [Switch]$FixEnv, [Switch]$Backup, [string]$BackupFolder = "C:\Temp\PathEnumeration", [Switch]$WhatIf, [Switch]$Passthru ) # Get all services $FixParameters = @() If ($FixServices) { $FixParameters += @{"Path" = "HKLM:\SYSTEM\CurrentControlSet\Services\" ; "ParamName" = "ImagePath" } } If ($FixUninstall) { $FixParameters += @{"Path" = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" ; "ParamName" = "UninstallString" } # If OS x64 - adding paths for x86 programs If (Test-Path "$($env:SystemDrive)\Program Files (x86)\") { $FixParameters += @{"Path" = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" ; "ParamName" = "UninstallString" } } } If ($Backup) { If (! (Test-Path $BackupFolder)) { New-Item $BackupFolder -Force -ItemType Directory | Out-Null } } $PTElements = @() ForEach ($FixParameter in $FixParameters) { Get-ChildItem $FixParameter.Path -ErrorAction SilentlyContinue | ForEach-Object { $SpCharREGEX = '([\[\]])' $RegistryPath = $_.name -Replace 'HKEY_LOCAL_MACHINE', 'HKLM:' -replace $SpCharREGEX, '`$1' $OriginalPath = (Get-ItemProperty "$RegistryPath") $ImagePath = $OriginalPath.$($FixParameter.ParamName) If ($FixEnv) { If ($($OriginalPath.$($FixParameter.ParamName)) -match '%(?''envVar''[^%]+)%') { $EnvVar = $Matches['envVar'] $FullVar = (Get-ChildItem env: | Where-Object { $_.Name -eq $EnvVar }).value $ImagePath = $OriginalPath.$($FixParameter.ParamName) -replace "%$EnvVar%", $FullVar Clear-Variable Matches } # End If } # End If $fixEnv # Get all services with vulnerability If (($ImagePath -like "* *") -and ($ImagePath -notLike '"*"*') -and ($ImagePath -like '*.exe*')) { # Skip MsiExec.exe in uninstall strings If ((($FixParameter.ParamName -eq 'UninstallString') -and ($ImagePath -NotMatch 'MsiExec(\.exe)?') -and ($ImagePath -Match '^((\w\:)|(%[-\w_()]+%))\\')) -or ($FixParameter.ParamName -eq 'ImagePath')) { $NewPath = ($ImagePath -split ".exe ")[0] $key = ($ImagePath -split ".exe ")[1] $trigger = ($ImagePath -split ".exe ")[2] $NewValue = '' # Get service with vulnerability with key in ImagePath If (-not ($trigger | Measure-Object).count -ge 1) { If (($NewPath -like "* *") -and ($NewPath -notLike "*.exe")) { $NewValue = "`"$NewPath.exe`" $key" } # End If # Get service with vulnerability with out key in ImagePath ElseIf (($NewPath -like "* *") -and ($NewPath -like "*.exe")) { $NewValue = "`"$NewPath`"" } # End ElseIf If ((-not ([string]::IsNullOrEmpty($NewValue))) -and ($NewPath -like "* *")) { try { $soft_service = $(if ($FixParameter.ParamName -Eq 'ImagePath') { 'Service' }Else { 'Software' }) $OriginalPSPathOptimized = $OriginalPath.PSPath -replace $SpCharREGEX, '`$1' Write-Output "$(get-date -format u) : Old Value : $soft_service : '$($OriginalPath.PSChildName)' - $($OriginalPath.$($FixParameter.ParamName))" Write-Output "$(get-date -format u) : Expected : $soft_service : '$($OriginalPath.PSChildName)' - $NewValue" if ($Passthru) { $PTElements += '' | Select-Object ` @{n = 'Name'; e = { $OriginalPath.PSChildName } }, ` @{n = 'Type'; e = { $soft_service } }, ` @{n = 'ParamName'; e = { $FixParameter.ParamName } }, ` @{n = 'Path'; e = { $OriginalPSPathOptimized } }, ` @{n = 'OriginalValue'; e = { $OriginalPath.$($FixParameter.ParamName) } }, ` @{n = 'ExpectedValue'; e = { $NewValue } } } If ($Backup) { $BcpFileName = "$BackupFolder\$soft_service`_$($OriginalPath.PSChildName)`_$(get-date -uFormat "%Y-%m-%d_%H%M%S").reg" $BcpTmpFileName = "$BackupFolder\$soft_service`_$($OriginalPath.PSChildName)`_$(get-date -uFormat "%Y-%m-%d_%H%M%S").tmp" $BcpRegistryPath = $RegistryPath -replace '\:' Write-Output "$(get-date -format u) : Creating registry backup : $BcpFileName" $ExportResult = REG EXPORT $BcpRegistryPath $BcpTmpFileName | Out-String Get-Content $BcpTmpFileName | Out-File $BcpFileName -Append Remove-Item $BcpTmpFileName -Force -ErrorAction "SilentlyContinue" Write-Output "$(get-date -format u) : Backup Result : $($ExportResult -split '\r\n' | Where-Object {$_ -NotMatch '^$'})" } If (! $WhatIf) { Set-ItemProperty -Path $OriginalPSPathOptimized -Name $($FixParameter.ParamName) -Value $NewValue -ErrorAction Stop $DisplayName = '' $keyTmp = (Get-ItemProperty -Path $OriginalPSPathOptimized) If ($soft_service -match 'Software') { $DisplayName = $keyTmp.DisplayName } If ($keyTmp.$($FixParameter.ParamName) -eq $NewValue) { Write-Output "$(get-date -format u) : SUCCESS : Path value was changed for $soft_service '$($OriginalPath.PSChildName)' $(if($DisplayName){"($DisplayName)"})" } # End If Else { Write-Output "$(get-date -format u) : ERROR : Something is going wrong. Path was not changed for $soft_service '$(if($DisplayName){$DisplayName}else{$OriginalPath.PSChildName})'." } # End Else } # End If } # End try Catch { Write-Output "$(get-date -format u) : ERROR : Something is going wrong. Value changing failed in service '$($OriginalPath.PSChildName)'." Write-Output "$(get-date -format u) : ERROR : $_" } # End Catch Clear-Variable NewValue } # End If } # End Main If } # End if (Skip not needed strings) } # End If If (($trigger | Measure-Object).count -ge 1) { Write-Output "$(get-date -format u) : ERROR : Can't parse $($OriginalPath.$($FixParameter.ParamName)) in registry $($OriginalPath.PSPath -replace 'Microsoft\.PowerShell\.Core\\Registry\:\:') " } # End If } # End Foreach } # End Foreach if ($Passthru) { return $PTElements } } Function Get-OSandPoShArchitecture { # Check OS architecture if ((Get-CimInstance Win32_OperatingSystem | Select-Object OSArchitecture).OSArchitecture -match "64.?bits?") { if ([intptr]::Size -eq 8) { Return $true, $true } else { Return $true, $false } } else { Return $false, $false } } Function Tee-Log { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $Input, [Parameter(Mandatory = $true)] $FilePath, [switch]$Silent ) if ($Silent) { $Input | Out-File -FilePath $LogName -Append } else { $Input | Tee-Object -FilePath $LogName -Append } } if ((! $FixServices) -and (! $FixUninstall)) { Throw "Should be selected at least one of two parameters: FixServices or FixUninstall. `r`n For more details use 'get-help Windows_Path_Enumerate.ps1 -full'" } if ($Help) { Write-Output "For help use this command in powershell: Get-Help $($MyInvocation.MyCommand.Path) -full" powershell -command "& Get-Help $($MyInvocation.MyCommand.Path) -full" exit } $OS, $PoSh = Get-OSandPoShArchitecture If (($OS -eq $true) -and ($PoSh -eq $true)) { $validation = "$(get-date -format u) : INFO : Executed x64 Powershell on x64 OS" } elseIf (($OS -eq $true) -and ($PoSh -eq $false)) { $validation = "$(get-date -format u) : WARNING : !ATTENTION! : Executed x32 Powershell on x64 OS. Not all vulnerabilities could be fixed.`r`n" $validation += "$(get-date -format u) : WARNING : For fixing all vulnerabilities should be used x64 Powershell." } else { $validation = "$(get-date -format u) : INFO : Executed x32 Powershell on x32 OS" } $DeleteLogFile = $false if ([string]::IsNullOrEmpty($LogName)) { # Log will be written to the temp file if file not specified $DeleteLogFile = $true $LogName = New-TemporaryFile } If (! (Test-Path $LogName)) { # If path does not exists it should be created try { $tmpLogPath = $LogName if ($tmpLogPath -NotMatch '[\\\/]$') { $tmpLogName = ($tmpLogPath -split '[\\\/]')[-1] $tmpLogPath = $tmpLogPath -replace "$tmpLogName`$" } else { $tmpLogName = 'ServicesFix-3.X.Log' } if (! (Test-Path $tmpLogPath)) { New-Item -Path $tmpLogPath -Force -ItemType Directory | Out-Null } New-Item -Path "$tmpLogPath\$tmpLogName" -Force -ItemType File | Out-Null $LogName = "$tmpLogPath\$tmpLogName" } catch { Throw "Log file '$LogName' does not exists and cannot be created. Error: $_" } } '*********************************************************************' | Tee-Log -FilePath $LogName -Silent:$Passthru "$(get-date -format u) : INFO : ComputerName: $($Env:ComputerName)" | Tee-Log -FilePath $LogName -Silent:$Passthru $validation | Tee-Log -FilePath $LogName -Silent:$Passthru if ($RestoreBackup) { if ($FixServices -and (! $FixUninstall)) { $RegexPart = "Service" } elseif ($FixUninstall -and (! $FixServices)) { $RegexPart = "Software" } elseif ($FixUninstall -and $FixServices) { $RegexPart = "(Service|Software)" } if (Test-Path $BackupFolderPath) { $FilesToImport = Get-ChildItem "$BackupFolderPath\" | Where-Object { $_.Name -match "$RegexPart`_.+_\d{4}-\d{1,2}-\d{1,2}_\d{3,6}\.reg$" } if ([string]::IsNullOrEmpty($FilesToImport)) { Write-Output "$(get-date -format u) : No backup files find in $BackupFolderPath" | Tee-Log -FilePath $LogName -Silent:$Passthru } else { Foreach ($FileToImport in $FilesToImport) { Write-Output "$(get-date -format u) : Importing '$($FileToImport.Name)' file to the registry" | Tee-Log -FilePath $LogName -Silent:$Passthru if ($WhatIf) { Write-Output "$(get-date -format u) : Whatif switch selected so nothing changed..." | Tee-Log -FilePath $LogName -Silent:$Passthru } else { REGEDIT /s $($FileToImport.FullName) } #Write-Output "$(get-date -format u) : Result : $($ImportResult -split '\r\n' | Where-Object {$_ -NotMatch '^$'})" | Tee-Log -FilePath $LogName -Silent:$Passthru } } } else { Write-Output "$(get-date -format u) : Backup folder does not exists. Nothing to restore..." | Tee-Log -FilePath $LogName -Silent:$Passthru } } else { $ScriptExecutionResult = Fix-ServicePath ` -FixUninstall:$FixUninstall ` -FixServices:$FixServices ` -WhatIf:$WhatIf ` -FixEnv:$FixEnv ` -Passthru:$Passthru ` -Backup:$CreateBackup ` -BackupFolder $BackupFolderPath if ($Passthru -and (! [string]::IsNullOrEmpty($ScriptExecutionResult))) { $Objects = $ScriptExecutionResult | Where-Object { $_.GetType().Name -eq 'PSCustomObject' } $ScriptExecutionResult = $ScriptExecutionResult | Where-Object { $_.GetType().Name -ne 'PSCustomObject' } } $ScriptExecutionResult | Tee-Log -FilePath $LogName -Silent:$Passthru If ($Passthru) { If ($Silent -and $(( $Objects | Measure-Object ).Count -ge 1)) { $True } ElseIf ($Silent) { $False } Else { $Objects } } } if ($DeleteLogFile) { Remove-Item $LogName -Force -ErrorAction "SilentlyContinue" }
Adding it to Intune
Below are the steps to add this to Intune:
- Go to Endpoint analytics – Microsoft Intune admin center
- Select Create script package
- Enter a Name (Microsoft Windows Unquoted Service Path, for example)
- Change the Publisher to PowerShellisfun, for example
- Select Next
- Click the Folder Icon on the Detection script file line and browse/select the saved Detection.ps1 file.
- Click the Folder Icon on the Remediation script file line and browse/select the saved Remediation.ps1 file.
- Set Run Script in 64-Bit PowerShell to Yes
- Select Next
- Select Scope tags if desired.
- Select Next
- Click Select Groups to include and select the group of devices that you want the scrips to run on.
- Configure the schedule of the group to run the script Daily or Hourly
- Select Next
- Review the Summary page and select Create
Check the Device status results to see the progress of the clients.
My advice would be to run the script on one of the clients that Microsoft Defender had a Security Recommendation on first, monitor the results, and expand on that until you feel confident to assign it to a (Dynamic) Group containing all your devices.
Log output
When checking the IntuneManamentExtension.log file on the assigned devices, you will see this when there is a service without quotation marks and that the Remediation script has run:
<![LOG[[HS] err output = ]LOG]!><time="11:45:23.4871809" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] std output = 2023-05-03 11:45:17Z : SUCCESS : Path value was changed for Service 'IntelAudioService']LOG]!><time="11:45:23.4871809" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] RemediationScript parsing result is done ]LOG]!><time="11:45:23.4871809" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] Returned exitcode from child process is 0]LOG]!><time="11:45:23.4871809" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] remediation script exit code is 0]LOG]!><time="11:45:23.4871809" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] Create output files successfully.]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] ACL output files successfully]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] currentPolicy.IsFirstPartyScript: False]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] Is managed installer policy: False]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] Processing policy with id = 42cf0975-c05b-4ba0-b02d-5ff33d1d1833]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] The policy needs be run as System]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS]: Enforce signature check = False, Running mode = 1]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG["C:\Program Files (x86)\Microsoft Intune Management Extension\agentexecutor.exe" -remediationScript ""C:\Windows\IMECache\HealthScripts\42cf0975-c05b-4ba0-b02d-5ff33d1d1833_1\detect.ps1"" ""C:\Windows\IMECache\HealthScripts\42cf0975-c05b-4ba0-b02d-5ff33d1d1833_1\47e138e1-0380-4f04-9533-ce6e6a7d9ca4_PreDetectScript.output"" ""C:\Windows\IMECache\HealthScripts\42cf0975-c05b-4ba0-b02d-5ff33d1d1833_1\47e138e1-0380-4f04-9533-ce6e6a7d9ca4_PreDetectScript.error"" ""C:\Windows\IMECache\HealthScripts\42cf0975-c05b-4ba0-b02d-5ff33d1d1833_1\47e138e1-0380-4f04-9533-ce6e6a7d9ca4_PreDetectScript.timeout"" 3600 "C:\Windows\System32\WindowsPowerShell\v1.0" 0 ""C:\Windows\IMECache\HealthScripts\42cf0975-c05b-4ba0-b02d-5ff33d1d1833_1\47e138e1-0380-4f04-9533-ce6e6a7d9ca4_PreDetectScript.exit"" False ""]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[Launch powershell executor in machine session]LOG]!><time="11:45:23.5032802" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[Create proxy process successfully.]LOG]!><time="11:45:23.5189918" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[process id = 3692]LOG]!><time="11:45:23.5189918" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[Powershell execution is done, exitCode = 0]LOG]!><time="11:45:24.5906888" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[Find 1 MDM certificates.]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] err output = ]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] std output = No Service found without quotes]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] PostDetectScript parsing result is done ]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] Returned exitcode from child process is 0]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file=""> <![LOG[[HS] the post detection script result for 42cf0975-c05b-4ba0-b02d-5ff33d1d1833 is True]LOG]!><time="11:45:29.6075923" date="5-3-2023" component="IntuneManagementExtension" context="" type="1" thread="36" file="">
Download the script(s) from GitHub here
Patience
Be aware that even though you configure the device group to check every hour, it can take a while to run and report back… So, patience is a virtue 😉
Pingback: Intune Newsletter - 12th May 2023 - Andrew Taylor
What about uninstall path detection?
The script in the link can fix those too, but as far as I know (correct me if I’m wrong) the fix was for the Active services?
What about detecting them, sorry my brain thought it but I didn’t type it. 🙂
I could update the detection script for that later this week 🤔 I’ve added it to my tasks 👍
Thanks! The other challenge is the remediation script needs a parameter to fix the uninstall path. The script probably needs to be cut up/recreated to do it. The script is just a monster!
Just to close the loop. Here’s a link to an Automox detection and remediation script for it and it works.
https://community.automox.com/community-worklets-12/windows-unquoted-search-path-remediation-1586
Ah, nice! Thanks for sharing, nice piece of code!
This isn’t working for me.
Just getting this in the log file and the script exits
PS C:\Repositories> . ‘C:\Repositories\Intune\Scripts\FixUnQuotedServices\Remediate_UnquotedServices.ps1’
2023-06-02 12:09:19Z : INFO : ComputerName: PC6395
2023-06-02 12:09:19Z : INFO : Executed x64 Powershell on x64 OS
If you run the detection script on that machine in ISE, what does it say? If there’s nothing to fix / no issues found, then running the remediation script will do nothing because of that