advapi32/ChangeServiceConfig.ps1

function ChangeServiceConfig {
    <#
    .SYNOPSIS
 
    Modifies a specified service's configuration.
 
    .DESCRIPTION
 
    Changes the configuration parameters of a service using the ChangeServiceConfig
    Win32 API call. Reflection is first used to retrieve the handle to the specified
    service (Get-ServiceConfigControlHandle), the specified modifications are made,
    and then CloseServiceHandle() is used to close the handle
 
    .PARAMETER Name
 
    An array of one or more service names to set the binary path for. Required.
 
    .PARAMETER Path
 
    The new binary path (lpBinaryPathName) to set for the specified service. Required.
 
    .NOTES
 
    Author: Will Schroeder (@harmj0y), Matthew Graeber (@mattifestation)
    License: BSD 3-Clause
    Required Dependencies: PSReflect
    Optional Dependencies: None
 
    (func advapi32 ChangeServiceConfig ([Bool]) @(
        [IntPtr], # _In_ SC_HANDLE hService
        [UInt32], # _In_ DWORD dwServiceType
        [UInt32], # _In_ DWORD dwStartType
        [UInt32], # _In_ DWORD dwErrorControl
        [String], # _In_opt_ LPCTSTR lpBinaryPathName
        [String], # _In_opt_ LPCTSTR lpLoadOrderGroup
        [IntPtr], # _Out_opt_ LPDWORD lpdwTagId
        [String], # _In_opt_ LPCTSTR lpDependencies
        [String], # _In_opt_ LPCTSTR lpServiceStartName
        [String], # _In_opt_ LPCTSTR lpPassword
        [String] # _In_opt_ LPCTSTR lpDisplayName
    ) -SetLastError -Charset Unicode)
 
    .LINK
 
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms681987(v=vs.85).aspx
 
    .EXAMPLE
 
    #>

    
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [OutputType('System.Boolean')]
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [Alias('ServiceName', 'Service')]
        [String[]]
        [ValidateNotNullOrEmpty()]
        $Name,

        [ValidateSet('FILE_SYSTEM_DRIVER', 'KERNEL_DRIVER', 'WIN32_OWN_PROCESS', 'WIN32_SHARE_PROCESS', 'INTERACTIVE_PROCESS', 'NO_CHANGE')]
        [String]
        $ServiceType = 'NO_CHANGE',

        [ValidateSet('AUTO_START', 'BOOT_START', 'DEMAND_START', 'DISABLED', 'SYSTEM_START', 'NO_CHANGE')]
        [String]
        $StartType = 'NO_CHANGE',

        [ValidateSet('ERROR_CRITICAL', 'ERROR_IGNORE', 'ERROR_NORMAL', 'ERROR_SEVERE', 'NO_CHANGE')]
        [String]
        $ErrorControl = 'NO_CHANGE',

        [Alias('BinaryPath', 'binPath', 'path')]
        [String]
        $BinaryPathName,

        # $LoadOrderGroup,

        # $TagId,

        # $Dependencies,

        [Alias('StartName')]
        [String]
        $ServiceStartName = '',

        [String]
        $Password = '',

        [String]
        $DisplayName = ''
    )

    BEGIN {
        filter Local:Get-ServiceConfigControlHandle {
            [OutputType([IntPtr])]
            Param(
                [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
                [ServiceProcess.ServiceController]
                [ValidateNotNullOrEmpty()]
                $TargetService
            )
            $GetServiceHandle = [ServiceProcess.ServiceController].GetMethod('GetServiceHandle', [Reflection.BindingFlags] 'Instance, NonPublic')
            $ConfigControl = 0x00000002
            $RawHandle = $GetServiceHandle.Invoke($TargetService, @($ConfigControl))
            $RawHandle
        }

        $ServiceTypeVal = Switch ($ServiceType) {
            'FILE_SYSTEM_DRIVER'    { 0x00000002 }
            'KERNEL_DRIVER'         { 0x00000001 }
            'WIN32_OWN_PROCESS'     { 0x00000010 }
            'WIN32_SHARE_PROCESS'   { 0x00000020 }
            'INTERACTIVE_PROCESS'   { 0x00000100 }
            'NO_CHANGE'             { [UInt32]::MaxValue }
        }

        $StartTypeVal = Switch ($StartType) {
            'AUTO_START'    { 0x00000002 }
            'BOOT_START'    { 0x00000000 }
            'DEMAND_START'  { 0x00000003 }
            'DISABLED'      { 0x00000004 }
            'SYSTEM_START'  { 0x00000001 }
            'NO_CHANGE'     { [UInt32]::MaxValue }
        }

        # [ValidateSet('ERROR_CRITICAL', 'ERROR_IGNORE', 'ERROR_NORMAL', 'ERROR_SEVERE', 'NO_CHANGE')]
        $ErrorControlVal = Switch ($ErrorControl) {
            'ERROR_CRITICAL'    { 0x00000003 }
            'ERROR_IGNORE'      { 0x00000000 }
            'ERROR_NORMAL'      { 0x00000001 }
            'ERROR_SEVERE'      { 0x00000002 }
            'NO_CHANGE'         { [UInt32]::MaxValue }
        }
    }

    PROCESS {

        ForEach($IndividualService in $Name) {

            $TargetService = Get-Service -Name $IndividualService -ErrorAction Stop
            try {
                $ServiceHandle = Get-ServiceConfigControlHandle -TargetService $TargetService
            }
            catch {
                $ServiceHandle = $Null
                Write-Warning "[ChangeServiceConfig] Error opening up the service handle with read control for $IndividualService : $_"
            }

            if ($ServiceHandle -and ($ServiceHandle -ne [IntPtr]::Zero)) {

                $Result = $Advapi32::ChangeServiceConfig($ServiceHandle, $ServiceTypeVal, $StartTypeVal, $ErrorControlVal, $BinaryPathName, '', [IntPtr]::Zero, '', $lpServiceStartName, $lpPassword, $lpDisplayName);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()

                if ($Result -ne 0) {
                    Write-Verbose "Successfully modified '$IndividualService'"
                    $True
                }
                else {
                    Write-Error ([ComponentModel.Win32Exception] $LastError)
                    $Null
                }

                $Null = $Advapi32::CloseServiceHandle($ServiceHandle)
            }
        }
    }
}