PowerShell: Stop Any Service on Windows!

Problem: some system protected services cannot be stopped.

PS C:\Users\KingKong> stop-service msmpsvc
Stop-Service : Service 'Microsoft Antimalware Service (msmpsvc)' cannot be stopped due to the following error: Cannot o
pen msmpsvc service on computer '.'.
At line:1 char:13
+ stop-service <<<< msmpsvc
+ CategoryInfo : CloseError: (System.ServiceProcess.ServiceController:ServiceController) [Stop-Service],
ServiceCommandException
+ FullyQualifiedErrorId : CouldNotStopService,Microsoft.PowerShell.Commands.StopServiceCommand

Solution: Ctrl-C & Ctrl-V

Update 2/1/20: This “solution” doesn’t work on all Windows services. For instance, it cannot stop many antivirus services. When I figure out a better method, I’ll update this post.

# Old version... deprecated.

function stopAnyService{
param(
[string]$serviceName
)

function getMatches([string]$serviceName){
$matches=get-service|?{$_.DisplayName -like "*$serviceName*" -or $_.Name -like "*$serviceName*" -or $_.Servicename -like "*$serviceName*"};
return $matches;
}

$matches=getMatches -service $serviceName;
if (!($matches)){
$message="$serviceName doesn't match anything on $env:computername";
write-host $message;
return $false;
}

function executeKillCommand($service){
.{
# Install Prerequisites
if (!(get-command setAcl -ErrorAction SilentlyContinue)){
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force;
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
choco install setacl -y;
}
if (!(get-command psexec -ErrorAction SilentlyContinue)){choco install sysinternals -y;}
}
$serviceName=$service.Name
if ($service.Status -ne "Stopped"){
# Try to stop the service as normal - only modify permissions and retry upon encountering errors
try{
Stop-Service $serviceName -Force -ErrorAction Stop;
}
catch{
$serviceRunas=(Get-WMIObject win32_service |?{$_ -like "*$serviceName*"}).StartName;

<# $serviceRunas=(Get-CIMInstance win32_service |?{$_ -like "*$serviceName*"}).StartName;
The term 'Get-CIMInstance' is not recognized as the name of a cmdlet, function, script file, or operable program. Check
the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:16
+ Get-CIMInstance <<<< win32_service
+ CategoryInfo : ObjectNotFound: (Get-CIMInstance:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
#>

write-host "$serviceName seems to be owned by $serviceRunas. Now seizing permissions...";

# Grant permissions of service to the Administrators group
$nullOutput=PSExec -s SetACL.exe -on $serviceName -ot srv -actn ace -ace 'n:Administrators;p:full' 2>&1; #redirect (>) 'stderr'(2) messages to 'stdout'(1); where 1 is a file descriptor (&), not a file
write-host "Process name $serviceName has been granted access to the Administrators group.";

# Retry stopping service
try{
Stop-Service $serviceName -Force;
write-host "$serviceName has been stopped successfully.";
return $true;
}
catch{
write-host $Error;
write-host "$serviceName has NOT been stopped successfully.";
return $false;
}
}

}else{
write-host "$serviceName is already stopped.";
return $true;
}
}

function initPsSessionAsAdmin{
function checkAdminPrivileges{
param($credential)
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$currentUser=$myWindowsID.Name
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator;
if ($myWindowsPrincipal.IsInRole($adminRole))
{
write-host "$currentUser is an Administrator. Program will now initiate a new session with such account...";
$Host.UI.RawUI.BackgroundColor = "Black";
return $true;
}
else
{
return $false;
}

}

function getAdminCredentials{
$exitLoop=$false;
do {
$credential= get-credential;
if(checkAdminPrivileges -credential $credential){$exitLoop=$true};
sleep 1;
}while ($exitLoop -eq $false)
return $credential;
}

if (!(checkAdminPrivileges)){
$cred=getAdminCredentials
try{
$session=New-PSSession -Credential $cred;
}catch{
# Enable WinRM as try block command didn't succeed;
start-process powershell.exe -credential $cred -nonewwindow -ArgumentList "enable-psremoting -force";
refreshenv;
$session=New-PSSession -Credential $cred;
}
}else{
try{
$session=New-PSSession;
}catch{
# Enable WinRM as try block command didn't succeed;
start-process powershell.exe -credential $cred -nonewwindow -ArgumentList "winrm quickconfig -force;"
refreshenv;
$session=New-PSSession;
}
}
if ($session){
return $session;
}else{
write-host "Could not proceed due to errors in the process of initiating a new PSSession.";
break;
}
}

function invokeKillCommand{
param($service)
if ($adminPsSession.State -ne "Opened"){$adminPsSession=initPsSessionAsAdmin;}
invoke-command -Session $adminPsSession -ScriptBlock{
param($importedFunc,$x)
[ScriptBlock]::Create($importedFunc).invoke($x);
} -args ${function:executeKillCommand},$service
Remove-PSSession $adminPsSession;
}

if($matches.count -gt 1){
#$message="Please rerun command with an exact match to one of these services:`r`n-----------------------------------------------------------------$($matches|ft -AutoSize|out-string)";
#write-host $message;

# This is a PowerShell 2.0 backward compatible technique to rebuild an object with a new column of Index values
$displayMatches=for ($i=0;$i -lt $matches.count;$i++){
$matches[$i]|Select-Object @{name='Index';e={$i}},Name,Displayname,Status
}
$displayMatches=$displayMatches|ft -AutoSize|Out-String
write-host "We have multiple matches for the $servicename`:`r`n-----------------------------------------------------------------$displayMatches";
$input=Read-Host "Please pick an index number from the above list"
write-host "Index value $input received.";
$matches=$matches[$input];
if (!($matches)){
write-host "Index value $input is invalid. No actions were taken.";
return $false;
}else{
$service=$matches;
invokeKillCommand -service $service;
}
}else{
$service=$matches;
invokeKillCommand -service $service;
}
}

stopAnyService -serviceName "MsMpSvc"

Sample Outputs:

<# Sample Outputs
PS C:\Users\KingKong> stopAnyService -serviceName MsMpSvc
Process name MsMpSvc has been granted access to the Administrators group.
MsMpSvc has been stopped successfully.
True

---------------------------------------------------------------------------------------------

PS C:\Users\KingKong> stopAnyService -serviceName "Audio"
We have multiple matches for the Audio:
-----------------------------------------------------------------
Index Name DisplayName Status
----- ---- ----------- ------
0 AudioEndpointBuilder Windows Audio Endpoint Builder Stopped
1 AudioSrv Windows Audio Stopped

Please pick an index number from the above list: 2
Index value 2 received.
Index value 2 is invalid. No actions were taken.
False
#>
function stopAnyService{
    param(
        [string]$serviceName,
        [string]$computerName=$env:computername
        )
    
    function executeKillCommand($serviceName){

        write-host "including prerequisites..."
        .{
            # Prerequisite commands
            $chocoAvailable="get-command choco -ErrorAction SilentlyContinue";
            $psexecAvailable="get-command psexec -ErrorAction SilentlyContinue";
            $setAclAvailable="get-command setacl -ErrorAction SilentlyContinue";

            if (!(Invoke-Expression $chocoAvailable)) {
                write-host "Installing Choco...";
                Set-ExecutionPolicy Bypass -Scope Process -Force;
                iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));
                }
            if (!(Invoke-Expression $chocoAvailable)) {
                write-host "Unable to install Chocolatey automation tool. Program now aborts.";
                break;
                }
            
            if(!(Invoke-Expression $psexecAvailable)){

                $pendingRebootTests = @(
                    @{
                        Name = 'RebootPending'
                        Test = { Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing' -Name 'RebootPending' -ErrorAction SilentlyContinue }
                        TestType = 'ValueExists'
                    }
                    @{
                        Name = 'RebootRequired'
                        Test = { Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Name 'RebootRequired' -ErrorAction SilentlyContinue }
                        TestType = 'ValueExists'
                    }
                    @{
                        Name = 'PendingFileRenameOperations'
                        Test = { Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue }
                        TestType = 'NonNullValue'
                    }
                )
                foreach ($test in $pendingRebootTests) {
                    $pendingReboot=Invoke-Command -ScriptBlock $test.Test
                    if($pendingReboot){
                        write-host "$env:computername currently has a pending reboot requirement. Aborting session...";
                        break;
                        }
                }

                try { 
                    $status = ([wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities").DetermineIfRebootPending()
                    if(($status -ne $null) -and $status.RebootPending){
                        write-host "$env:computername currently has a pending reboot requirement. Aborting session...";
                        break;
                        }
                    }catch{}

                write-host "Installing PSExec...";
                choco install sysinternals -y -force;
                }
            if (!(Invoke-Expression $psexecAvailable)) {
                write-host "Unable to install psexec. Program now aborts.";
                break;
                }

            if(!(Invoke-Expression $setAclAvailable)){
                write-host "Installing setacl...";
                choco install setacl -y -force;
                }
             if (!(Invoke-Expression $setAclAvailable)) {
                write-host "Unable to install setACL. Program now aborts.";
                break;
                }                          
            write-host "Done.";

        }

        function forceKillService ($service){
        if ($service.Status -ne "Stopped"){
            # Try to stop the service as normal - only modify permissions and retry upon encountering errors
            try{
                Stop-Service $serviceName -Force -ErrorAction Stop;
                }
            catch{
                $serviceRunas=(Get-WMIObject win32_service |?{$_ -like "*$serviceName*"}).StartName;

                <# $serviceRunas=(Get-CIMInstance win32_service |?{$_ -like "*$serviceName*"}).StartName;
                The term 'Get-CIMInstance' is not recognized as the name of a cmdlet, function, script file, or operable program. Check
                 the spelling of the name, or if a path was included, verify that the path is correct and try again.
                At line:1 char:16
                + Get-CIMInstance <<<<  win32_service
                    + CategoryInfo          : ObjectNotFound: (Get-CIMInstance:String) [], CommandNotFoundException
                    + FullyQualifiedErrorId : CommandNotFoundException
                #>

                write-host "$serviceName seems to be owned by $serviceRunas. Now seizing permissions...";

                # Grant permissions of service to the Administrators group
                $nullOutput=PSExec -s SetACL.exe -on $serviceName -ot srv -actn ace -ace 'n:Administrators;p:full' 2>&1; #redirect (>) 'stderr'(2) messages to 'stdout'(1); where 1 is a file descriptor (&), not a file
                write-host "Process name $serviceName has been granted access to the Administrators group.";

                # Retry stopping service
                try{
                    Stop-Service $serviceName -Force;
                    write-host "$serviceName has been stopped successfully.";
                    return $true;
                    }
                catch{
                    write-host $Error;
                    write-host "$serviceName has NOT been stopped successfully.";
                    return $false;
                    }
                }

            }else{
                write-host "$serviceName is already stopped.";
                return $true;
                }
        }
        
        $matches=get-service|?{$_.DisplayName -like "*$serviceName*" -or $_.Name -like "*$serviceName*" -or $_.Servicename -like "*$serviceName*"};
        #$matches=get-service|?{$_.DisplayName -like "*$serviceName*" -or $_.Name -like "*$serviceName*"};
        if (!($matches)){
            $message="$serviceName doesn't match anything on $env:computername";
            write-host $message;
            return $false;
            }
        
        if($matches.count -gt 1){
            # This is a PowerShell 2.0 backward compatible technique to rebuild an object with a new column of Index values
            $displayMatches=for ($i=0;$i -lt $matches.count;$i++){
                $matches[$i]|Select-Object @{name='Index';e={$i}},Name,Displayname,Status
                }
            $displayMatches=$displayMatches|ft -AutoSize|Out-String
            write-host "We have multiple matches for the $servicename`:`r`n-----------------------------------------------------------------$displayMatches";
            $input=Read-Host "Please pick an index number from the above list"
            write-host "Index value $input received.";
            $matches=$matches[$input];
            if (!($matches)){
                write-host "Index value $input is invalid. No actions were taken.";
                return $false;
                }else{
                    $service=$matches;
                    forceKillService $service;
                    return $true;
                    }        
            }else{
                $service=$matches;         
                forceKillService $service;
                return $true;
                }
        
    }

    function initPsSessionAsAdmin($computerName){
        function checkAdminPrivileges{
            param($credential)
            $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
            $currentUser=$myWindowsID.Name
            $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
            $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator;
            if ($myWindowsPrincipal.IsInRole($adminRole))
               {
               write-host "$currentUser is an Administrator. Program will now initiate a new session with such account...";
               $Host.UI.RawUI.BackgroundColor = "Black";
               return $true;
               }
            else
               {
               return $false;
               }

            }

        function getAdminCredentials{
            $exitLoop=$false;
            do {
                $credential= get-credential;
                if(checkAdminPrivileges -credential $credential){$exitLoop=$true};
                sleep 1;
                }while ($exitLoop -eq $false)
            return $credential;
            }

        if (!(checkAdminPrivileges)){
            $cred=getAdminCredentials
            try{
                $session=New-PSSession -Credential $cred -ComputerName $computerName;
                }catch{
                    # Enable WinRM as try block command didn't succeed;
                    start-process powershell.exe -credential $cred -nonewwindow -ArgumentList "enable-psremoting -force";
                    refreshenv;
                    $session=New-PSSession -Credential $cred -ComputerName $computerName;
                    }
            }else{
                try{
                $session=New-PSSession -ComputerName $computerName;
                }catch{                    
                    # Enable WinRM as try block command didn't succeed;
                    start-process powershell.exe -credential $cred -nonewwindow -ArgumentList "winrm quickconfig -force;"
                    refreshenv;
                    $session=New-PSSession -ComputerName $computerName;
                    }                
                }
        if ($session){
            return $session;
            }else{
                write-host "Could not proceed due to errors in the process of initiating a new PSSession.";
                break;
                }
    }

    function invokeKillCommand{
        param($service)

        # Cleanup any lingering PS Sessions
        get-pssession|remove-pssession;

        if (!$adminPsSession -or $adminPsSession.State -eq "Closed"){$adminPsSession=initPsSessionAsAdmin -computerName $computerName;}       

        if(!(get-command psexec -ea SilentlyContinue)){
            if (!(get-command choco -ea SilentlyContinue)) {
                write-host "Installing Choco...";
                Set-ExecutionPolicy Bypass -Scope Process -Force;
                iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));
                }

            write-host "Installing PSExec...";
            choco install sysinternals -y -force;
            }

        # Setting WinRM memory size to ensure success
        #Correct format: winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="$ramMB"}'
        #Alternaltive: psexec \\$computerName PowerShell Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024 #This one runs into permission issues
        [int]$ramMB=(gwmi -Class win32_operatingsystem -computername $computerName -ea stop).TotalVisibleMemorySize/1024
        if ($ramMB -ge 4090){$ramMB=4090}
        $nullOutput=invoke-expression "psexec \\$computerName -s winrm.cmd set winrm/config/winrs '@{MaxMemoryPerShellMB=`"$ramMB`"}'" 2>&1;

        invoke-command -Session $adminPsSession -ScriptBlock{
            param($importedFunc,$x)
            [ScriptBlock]::Create($importedFunc).invoke($x); 
            } -args ${function:executeKillCommand},$service
        Remove-PSSession $adminPsSession;
    }

    function killService{
        param($service)
        $computerNameRegex='^(.*?)\.'
        $local=$([void]($computerName -match $computerNameRegex);if($matches){$matches[1]}else{$computerName}) -like $env:computername;
        if ($local){
            executeKillCommand -service $service;                    
            }else{
                invokeKillCommand -service $service;
                }
        }
    killService -service $serviceName
}
stopAnyService -serviceName "MsMpSvc" -computerName $env:computername

Leave a Reply

Your email address will not be published. Required fields are marked *