Public/Enable-ServicePathQuoting.ps1

function Enable-ServicePathQuoting {
    <#
    .SYNOPSIS
        Adds quotation marks around a Windows service's executable path.
    .DESCRIPTION
        Reads the ImagePath registry value for the specified service and wraps the executable
        path in quotation marks, mitigating the unquoted service path privilege escalation
        vulnerability. Arguments that follow the executable are preserved. After updating the
        registry the service is restarted and its status is verified.

        Requires elevation (Administrator). Use -WhatIf to preview changes without applying them.
        Remote operations use Invoke-Command and require WinRM to be configured on the target machine.
    .INPUTS
        None. Parameters must be supplied directly.
    .OUTPUTS
        System.Management.Automation.PSCustomObject
    .PARAMETER Name
        The service name (as returned by Get-Service).
    .PARAMETER DisplayName
        The display name of the service.
    .PARAMETER ComputerName
        The target computer. Defaults to the local machine.
    .EXAMPLE
        Enable-ServicePathQuoting -Name 'VulnerableSvc'

        Quotes the ImagePath for VulnerableSvc on the local machine and restarts it.
    .EXAMPLE
        Enable-ServicePathQuoting -Name 'VulnerableSvc' -ComputerName 'Server01'

        Quotes the ImagePath for VulnerableSvc on Server01 and restarts it.
    .EXAMPLE
        Enable-ServicePathQuoting -DisplayName 'My Vulnerable Service' -WhatIf

        Previews the quoting change without applying it.
    .NOTES
        Modifying service registry keys requires Administrator privileges.
        The service is restarted after the path is updated. Ensure this is acceptable before running.
        Remote operations require WinRM to be configured on the target machine.
        Use Disable-ServicePathQuoting to reverse this change.
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([System.Management.Automation.PSCustomObject])]

    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'ByName', Position = 0)]
        [string]$Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByDisplayName', Position = 0)]
        [string]$DisplayName,

        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $isLocal = ($ComputerName -ieq $env:COMPUTERNAME) -or
               ($ComputerName -ieq 'localhost') -or
               ($ComputerName -eq '127.0.0.1')

    $svcParams = @{ ErrorAction = 'Stop' }
    if ($PSCmdlet.ParameterSetName -eq 'ByDisplayName') {
        $svcParams.DisplayName = $DisplayName
    } else {
        $svcParams.Name = $Name
    }
    if (-not $isLocal) {
        $svcParams.ComputerName = $ComputerName
    }
    $service = Get-Service @svcParams

    $serviceName  = $service.Name
    $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName"

    if ($isLocal) {
        $currentImagePath = (Get-ItemProperty -Path $registryPath -Name ImagePath -ErrorAction Stop).ImagePath
    } else {
        $currentImagePath = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
            (Get-ItemProperty -Path $using:registryPath -Name ImagePath -ErrorAction Stop).ImagePath
        }
    }

    $previousPath = $currentImagePath

    if ($currentImagePath.TrimStart().StartsWith('"')) {
        Write-Warning "The ImagePath for '$serviceName' is already quoted. No changes made."
        return
    }

    if ($currentImagePath -imatch '^(.*?\.exe)(.*)$') {
        $exePath      = $Matches[1].Trim()
        $arguments    = $Matches[2].Trim()
        $newImagePath = if ($arguments) { "`"$exePath`" $arguments" } else { "`"$exePath`"" }
    } else {
        $newImagePath = "`"$currentImagePath`""
    }

    if ($PSCmdlet.ShouldProcess("$ComputerName\$serviceName", "Set ImagePath to '$newImagePath' and restart service")) {
        if ($isLocal) {
            Set-ItemProperty -Path $registryPath -Name ImagePath -Value $newImagePath -ErrorAction Stop
            Write-Verbose "Registry updated for '$serviceName'. Restarting..."
            Restart-Service -Name $serviceName -Force -ErrorAction Stop
            $newStatus = (Get-Service -Name $serviceName).Status
        } else {
            Invoke-Command -ComputerName $ComputerName -ScriptBlock {
                Set-ItemProperty -Path $using:registryPath -Name ImagePath -Value $using:newImagePath -ErrorAction Stop
                Restart-Service -Name $using:serviceName -Force -ErrorAction Stop
            }
            Write-Verbose "Registry updated and '$serviceName' restarted on '$ComputerName'. Verifying status..."
            $newStatus = (Get-Service -Name $serviceName -ComputerName $ComputerName).Status
        }

        if ($newStatus -ne 'Running') {
            Write-Warning "Service '$serviceName' on '$ComputerName' did not return to Running after restart. Current status: $newStatus"
        } else {
            Write-Verbose "Service '$serviceName' on '$ComputerName' is Running."
        }

        [PSCustomObject]@{
            ComputerName = $ComputerName
            ServiceName  = $serviceName
            PreviousPath = $previousPath
            NewPath      = $newImagePath
        }
    }
}