Check if a Windows service exists and delete in PowerShell - powershell

I am currently writing a deployment script that installs a number of Windows services.
The services names are versioned, so I want to delete the prior Windows service version as part of the installs of the new service.
How can I best do this in PowerShell?

You can use WMI or other tools for this since there is no Remove-Service cmdlet until Powershell 6.0 (See Remove-Service doc)
For example:
$service = Get-WmiObject -Class Win32_Service -Filter "Name='servicename'"
$service.delete()
Or with the sc.exe tool:
sc.exe delete ServiceName
Finally, if you do have access to PowerShell 6.0:
Remove-Service -Name ServiceName

There's no harm in using the right tool for the job, I find usign sc.exe (via PowerShell) to be the most reliable method with few dependencies.
Local:
sc.exe delete "MyService"
Remote server:
sc.exe \\server delete "MyService"

If you just want to check service existence:
if (Get-Service "My Service" -ErrorAction SilentlyContinue)
{
"service exists"
}

I used the "-ErrorAction SilentlyContinue" solution but then later ran into the problem that it leaves an ErrorRecord behind. So here's another solution to just checking if the Service exists using "Get-Service".
# Determines if a Service exists with a name as defined in $ServiceName.
# Returns a boolean $True or $False.
Function ServiceExists([string] $ServiceName) {
[bool] $Return = $False
# If you use just "Get-Service $ServiceName", it will return an error if
# the service didn't exist. Trick Get-Service to return an array of
# Services, but only if the name exactly matches the $ServiceName.
# This way you can test if the array is emply.
if ( Get-Service "$ServiceName*" -Include $ServiceName ) {
$Return = $True
}
Return $Return
}
[bool] $thisServiceExists = ServiceExists "A Service Name"
$thisServiceExists
But ravikanth has the best solution since the Get-WmiObject will not throw an error if the Service didn't exist. So I settled on using:
Function ServiceExists([string] $ServiceName) {
[bool] $Return = $False
if ( Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" ) {
$Return = $True
}
Return $Return
}
So to offer a more complete solution:
# Deletes a Service with a name as defined in $ServiceName.
# Returns a boolean $True or $False. $True if the Service didn't exist or was
# successfully deleted after execution.
Function DeleteService([string] $ServiceName) {
[bool] $Return = $False
$Service = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'"
if ( $Service ) {
$Service.Delete()
if ( -Not ( ServiceExists $ServiceName ) ) {
$Return = $True
}
} else {
$Return = $True
}
Return $Return
}

More recent versions of PS have Remove-WmiObject. Beware of silent fails for $service.delete() ...
PS D:\> $s3=Get-WmiObject -Class Win32_Service -Filter "Name='TSATSvrSvc03'"
PS D:\> $s3.delete()
...
ReturnValue : 2
...
PS D:\> $?
True
PS D:\> $LASTEXITCODE
0
PS D:\> $result=$s3.delete()
PS D:\> $result.ReturnValue
2
PS D:\> Remove-WmiObject -InputObject $s3
Remove-WmiObject : Access denied
At line:1 char:1
+ Remove-WmiObject -InputObject $s3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Remove-WmiObject], ManagementException
+ FullyQualifiedErrorId : RemoveWMIManagementException,Microsoft.PowerShell.Commands.RemoveWmiObject
PS D:\>
For my situation I needed to be running 'As Administrator'

To delete multiple services in Powershell 5.0, since remove service does not exist in this version
Run the below command
Get-Service -Displayname "*ServiceName*" | ForEach-object{ cmd /c sc delete $_.Name}

One could use Where-Object
if ((Get-Service | Where-Object {$_.Name -eq $serviceName}).length -eq 1) {
"Service Exists"
}

Combining Dmitri & dcx's answers I made this:
function Confirm-WindowsServiceExists($name)
{
if (Get-Service $name -ErrorAction SilentlyContinue)
{
return $true
}
return $false
}
function Remove-WindowsServiceIfItExists($name)
{
$exists = Confirm-WindowsServiceExists $name
if ($exists)
{
sc.exe \\server delete $name
}
}

For single PC:
if (Get-Service "service_name" -ErrorAction 'SilentlyContinue'){(Get-WmiObject -Class Win32_Service -filter "Name='service_name'").delete()}
else{write-host "No service found."}
Macro for list of PCs:
$name = "service_name"
$list = get-content list.txt
foreach ($server in $list) {
if (Get-Service "service_name" -computername $server -ErrorAction 'SilentlyContinue'){
(Get-WmiObject -Class Win32_Service -filter "Name='service_name'" -ComputerName $server).delete()}
else{write-host "No service $name found on $server."}
}

To check if a Windows service named MySuperServiceVersion1 exists, even when you might not be sure of its exact name, you could employ a wildcard, using a substring like so:
if (Get-Service -Name "*SuperService*" -ErrorAction SilentlyContinue)
{
# do something
}

PowerShell Core (v6+) now has a Remove-Service cmdlet.
I don't know about plans to back-port it to Windows PowerShell, where it is not available as of v5.1.
Example:
# PowerShell *Core* only (v6+)
Remove-Service someservice
Note that invocation fails if the service doesn't exist, so to only remove it if it currently exists, you could do:
# PowerShell *Core* only (v6+)
$name = 'someservice'
if (Get-Service $name -ErrorAction Ignore) {
Remove-Service $name
}

Adapted this to take an input list of servers, specify a hostname and give some helpful output
$name = "<ServiceName>"
$servers = Get-content servers.txt
function Confirm-WindowsServiceExists($name)
{
if (Get-Service -Name $name -Computername $server -ErrorAction Continue)
{
Write-Host "$name Exists on $server"
return $true
}
Write-Host "$name does not exist on $server"
return $false
}
function Remove-WindowsServiceIfItExists($name)
{
$exists = Confirm-WindowsServiceExists $name
if ($exists)
{
Write-host "Removing Service $name from $server"
sc.exe \\$server delete $name
}
}
ForEach ($server in $servers) {Remove-WindowsServiceIfItExists($name)}

For PowerShell versions prior to v6, you can do this:
Stop-Service 'YourServiceName'; Get-CimInstance -ClassName Win32_Service -Filter "Name='YourServiceName'" | Remove-CimInstance
For v6+, you can use the Remove-Service cmdlet.
Observe that starting in Windows PowerShell 3.0, the cmdlet Get-WmiObject has been superseded by Get-CimInstance.

Windows Powershell 6 will have Remove-Service cmdlet.
As of now the Github release shows PS v6 beta-9
Source: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/remove-service?view=powershell-6

I know it's an old question but if someone is looking for one-liner:
On PS Version > 7.2
Wildcard search on the Name property
Get-Service *name* | Select-Object -First 1 | Remove-Service
Search the Display Name property
Get-Service -DisplayName "My Service Description" | Remove-Service

Related

It returns all services even though there is one in list

I want my script to take the server and service as a list from a .txt file. After that I want my script to check that if this service exists on the servers on the txt file.
But when I run this script, it returns all of the services as it exists on the server and not the ones I specified in service list. Also it doesn't drop to catch even tho the service doesn't exist.
Can you tell me why it returns all of the services?
$ErrorActionPreference='stop'
$ServerList = Get-Content 'C:\Users\username\Desktop\service test\servers.txt'
$ServiceList = Get-Content 'C:\Users\username\Desktop\service test\services.txt'
try{
foreach($Server in $ServerList){
foreach($Service in $ServiceList){
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
Get-Service -Name $Service
}
foreach($List in $Result){
Write-Host "$List exists on $Server"
}
}
}
}
catch [System.Management.Automation.ActionPreferenceStopException]
{
Write-Host "Error"
}
Continuing from my comment. . . when using a local variable against a remote computer in a scriptblock, you have to pass it as an argument, or have it referenced using the remote variable of $using.
This is due to a new session being ran on the remote machine with no clue what $service is on that machine as it's never been declared on that side.
You can pass it using the -Arguments, parameter. Creating a param block to pass it onto or, using the remote variable of $using.
Also, there's really no need for you to invoke the command over to the remote machine as Get-Service has a -ComputerName parameter for remote machines:
$ServerList = Get-Content 'C:\Users\username\Desktop\service test\servers.txt'
$ServiceList = Get-Content 'C:\Users\username\Desktop\service test\services.txt'
foreach ($Server in $ServerList)
{
foreach ($Service in $ServiceList.trim())
{
[PSCustomObject]#{
ComputerName = $Server
Service = $Service
Exists = if (Get-Service -Name $service -ComputerName $Server -ErrorAction SilentlyContinue) { $true } else { $false }
}
}
}
as for what you tried:
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
Get-Service -Name $Service
}
$service (as mentioned above), is empty and not defined on the remote sessions scope.
When you switched it to $using:service it worked but, it returned the type of object and not the name itself, because you're returning the entirety of an object and not the name property. Instead, just reference the current $service that is being used in the loop and declare if it's there, or not.
the script is not structured correctly, an no need to us the invoke command, when you can use 'get-service -computername' Also the try, catch statement would only catch the last error not each.
I changed your original script to reflect this and moved the try, catch statement to catch each error (if the service does not exist).
$ErrorActionPreference='stop'
$ServerList = Get-Content 'C:\temp\servicetest\servers.txt'
$ServiceList = Get-Content 'C:\temp\servicetest\services.txt'
ForEach($Server in $ServerList){
#Get-Service -ComputerName $Server -Name 'XblAuthManager'
ForEach($Service in $ServiceList){
try {
$a = Get-Service -ComputerName $Server -Name $Service
IF ($a) {
Write-Host "Service - $Service exists on Server - $Server"
}
} catch {
Write-Host "Service - $Service does not exist on Server - $Server"
}
}
}

Disable Print Spooler Script [duplicate]

I came across this one liner that appears to work:
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Can anyone explain why, because I thought stop-service didn't work unless you either used remoting or it occurred on the local host.
The output of Get-Service is a System.ServiceProcess.ServiceController .NET class that can operate on remote computers. How it accomplishes that, I don't know - probably DCOM or WMI. Once you've gotten one of these from Get-Service, it can be passed into Stop-Service which most likely just calls the Stop() method on this object. That stops the service on the remote machine. In fact, you could probably do this as well:
(get-service -ComputerName remotePC -Name Spooler).Stop()
Thanks to everyone's contributions to this question, I've come up with the following script. Change the values for $SvcName and $SvrName to suit your needs. This script will start the remote service if it is stopped, or stop it if it is started. And it uses the cool .WaitForStatus method to wait while the service responds.
#Change this values to suit your needs:
$SvcName = 'Spooler'
$SvrName = 'remotePC'
#Initialize variables:
[string]$WaitForIt = ""
[string]$Verb = ""
[string]$Result = "FAILED"
$svc = (get-service -computername $SvrName -name $SvcName)
Write-host "$SvcName on $SvrName is $($svc.status)"
Switch ($svc.status) {
'Stopped' {
Write-host "Starting $SvcName..."
$Verb = "start"
$WaitForIt = 'Running'
$svc.Start()}
'Running' {
Write-host "Stopping $SvcName..."
$Verb = "stop"
$WaitForIt = 'Stopped'
$svc.Stop()}
Default {
Write-host "$SvcName is $($svc.status). Taking no action."}
}
if ($WaitForIt -ne "") {
Try { # For some reason, we cannot use -ErrorAction after the next statement:
$svc.WaitForStatus($WaitForIt,'00:02:00')
} Catch {
Write-host "After waiting for 2 minutes, $SvcName failed to $Verb."
}
$svc = (get-service -computername $SvrName -name $SvcName)
if ($svc.status -eq $WaitForIt) {$Result = 'SUCCESS'}
Write-host "$Result`: $SvcName on $SvrName is $($svc.status)"
}
Of course, the account you run this under will need the proper privileges to access the remote computer and start and stop services. And when executing this against older remote machines, you might first have to install WinRM 3.0 on the older machine.
Based on the built-in Powershell examples, this is what Microsoft suggests. Tested and verified:
To stop:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StopService()
To start:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StartService()
This worked for me, but I used it as start. powershell outputs,
waiting for service to finshing starting a few times then finishes and then a get-service on the remote server shows the service started.
**start**-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Another option; use invoke-command:
cls
$cred = Get-Credential
$server = 'MyRemoteComputer'
$service = 'My Service Name'
invoke-command -Credential $cred -ComputerName $server -ScriptBlock {
param(
[Parameter(Mandatory=$True,Position=0)]
[string]$service
)
stop-service $service
} -ArgumentList $service
NB: to use this option you'll need PowerShell to be installed on the remote machine and for the firewall to allow requests through, and for the Windows Remote Management service to be running on the target machine. You can configure the firewall by running the following script directly on the target machine (one off task): Enable-PSRemoting -force.
You can also do (Get-Service -Name "what ever" - ComputerName RemoteHost).Status = "Stopped"
You could just run a foreach and have logging enabled.
The console will show if something goes wrong and you can look in the log.
That way, you can then handle the errors individually.
I think it works better this way than running a Test-Netconnection for the verification part because firewall rules can create the value false.
For this example you ned a csv file with column ServerName, Populate the column with servername.contoso.com
$ServerList = "$PSScriptRoot\Serverlist.csv"
$Transcriptlog = "$PSScriptRoot\Transcipt.txt"
Start-Transcript -Path $Transcriptlog -Force
Get-Date -Format "yyyy/MM/dd HH:mm"
Try
{ # Start Try
$ImportServerList = Import-Csv $ServerList -Encoding UTF8 | ForEach-Object { # Start Foreach
New-Object PsObject -Prop #{ # Start New-Object
ServerName = $_.ServerName } # End NewObject
Invoke-Command -ComputerName $_.ServerName -ErrorAction Continue -ScriptBlock { # Start ScriptBlock
# Disable Service PrintSpooler
Get-Service -Name Spooler | Stop-Service -Force
} # End ScriptBlock
} # End Foreach
} # End Try
Catch
{ # Start Catch
Write-Warning -Message "## ERROR## "
Write-Warning -Message "## Script could not start ## "
Write-Warning $Error[0]
} # End Catch
Stop-Transcript
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
This fails because of your variables
-ComputerName remotePC needs to be a variable $remotePC or a string "remotePC"
-Name Spooler(same thing for spooler)
As far as I know, and I cant verify it now, you cannot stop remote services with the Stop-Service cmdlet or with .Net, it is not supported.
Yes it works, but it stopes the service on your local machine, not on the remote computer.
Now, if the above is correct, without remoting or wmi enabled, you could set a scheduled job on the remote system, using AT, that runs Stop-Service locally.

Power Shell Get Service Issue

Hi I need to get a script that will do the following:
Check if a service exists
If the service doesn't exist run my script
If the service exists do nothing
Here is what I have but it's not working for me:
$service = Get-WmiObject -Class Win32_Service -Filter "Name='servicename'"
if($service.Status -eq $NULL)
{
$CLID = $inclid
New-Item -Path "c:\" -Name "folder" -ItemType "directory"
Invoke-WebRequest -Uri https://something.com\setup.exe -OutFile c:\folder\swibm#$CLID#101518#.exe
$installer = "swibm#$CLID#101518#.exe"
Start-Process -FilePath $installer -WorkingDirectory "C:\folder"
}
else
{
Write-Host "Client Already Installed"
}
If I run $service.Status alone I get an "OK" returned. Under this condition I would need the script to stop and run the else section. I only want this script to run if $service.Status returns nothing. Where am I going wrong here?
You may try putting $null on the left hand side of the comparison.
If($null -eq $services.status)
Here is a nice write up discussing it
Simpler way to check if service exists:
if( Get-WmiObject -Class Win32_Service -Filter "Name='servicename'" ) {
# Service exists
}
else {
# Service doesn't exist
}
... or use the Get-Service cmdlet:
if( Get-Service -ErrorAction SilentlyContinue -Name servicename ) {
# Service exists
}
else {
# Service doesn't exist
}

I'm trying to run a powershell command to uninstall a WinPcap, but after trying trying different methods I'm still not succesfull?

Below is the script that I'm running that always gives either a Program error or a The term 'x86' is not recognized as the name of a cmdlet error. The service stops but the Invoke-Command seems to be the problem.
$Servers = Get-Content "C:\DTServerScript\Servers.txt"
ForEach ($Server in $Servers) {
$Status = ""
do
{
#Stopping Service
$ServiceAgent = Get-Service -ComputerName $Server | Where {$_.Name -like "*oneagent*"}
Write-Host "Pending Stop on $Server"
Stop-Service $ServiceAgent
sleep 1
$Status = $ServiceAgent.Status
} until ($Status -eq "Stopped")
Write-Host "Service state is $Status on $Server"
# Execute config change
# Invoking Commands on Server
Invoke-Command -ComputerName $Server {cmd.exe/ c:\Program Files (x86)\WinPcap\uninstall.exe /S }
write-host "Service is starting on $Server"
# Starting Service
Start-Service $ServiceAgent
$ServiceAgent = Get-Service -ComputerName $Server | Where {$_.Name -like "*oneagent*"}
$Status = $ServiceAgent.Status
}
Try quoting the path and using braces around the name containing ()..
Invoke-Command -ComputerName $Server { & $Env:ComSpec /C "${Env:ProgramFiles(x86)}\WinPcap\uninstall.exe" /S }

How to Disable all BizTalk Hostinstances with PowerShell script

I'm trying to make a PowerShell script to stop and disable from starting all BizTalk host instances.
Stopping is no problem with this code:
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 1) -and ($hostinst.ServiceState -ne 8) ) {
Write-Host "Stop Hostinstance" $hostinst.HostName
$hostinst.Stop()
Write-Host "Hostinstance" $hostinst.HostName "stopped"
}
}
But now I'm trying to Disable all stopped Host Instances from starting up.
My first try gives no error but doesn't do anything.
All host instance are mentioned in the output but they are not disabled.
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -eq 1) -or ($hostinst.ServiceState -eq 8) ) {
Write-Host "disable Hostinstance" $hostinst.HostName
$hostinst.IsDisabled = $true
Write-Host "Hostinstance" $hostinst.HostName "is disabled"
}
}
My second try gives an error because of the -path parameter.
$hosts = Get-WmiObject MSBTS_HostInstance -namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -eq 1) -or ($hostinst.ServiceState -eq 8) ) {
Write-Host "disable Hostinstance" $hostinst.HostName
Set-ItemProperty -Path $hostinst__PATH -Name IsDisabled -Value $True
# $hostinst.IsDisabled = $true
Write-Host "Hostinstance" $hostinst.HostName "is disabled"
}
}
What is the correct method to set the property IsDisabled to $true or to $false?
BizTalk host instance is a windows service. So you can use powershell's Get-Service cmdlet to stop and disable it.
Suppose your host name is BizTalkServerApplication
Then following script will do the job:
Get-Service -Name BTSSvc`$BizTalkServerApplication | Stop-Service -PassThru | Set-Service -StartupType disabled
Also note the "$" is escaped as "`$"
#Zee is close, but still needs to loop through.. Here's one way to do that - concat the host's name with BTSSvc
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 8) ) { # ignore isolated hosts
$svcName = ('BTSSvc${0}' -f $hostinst.HostName) # get something Get-Service can work with
Get-Service -Name $svcName | Stop-Service -PassThru | Set-Service -StartupType disabled
Write-Host "Hostinstance" $hostinst.HostName "stopped and disabled"
}
}
And if you need to set them back to Automatic/started:
$hosts = Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer'
foreach($hostinst in $hosts) {
if ( ($hostinst.ServiceState -ne 8) ) { # ignore isolated hosts
$svcName = ('BTSSvc${0}' -f $hostinst.HostName) # get something Get-Service can work with
Get-Service -Name $svcName | Set-Service -StartupType Automatic -PassThru | Start-Service
Write-Host "Hostinstance" $hostinst.HostName "stopped and disabled"
}
}
And here's a way that might be even a little clearer:
Get-Service | Where-Object { $_.Name.StartsWith("BTSSvc") } | Stop-Service -PassThru | Set-Service -StartupType Disabled
Thanks for your answers.
However there are 2 reasons why this doesn't work completely fine in a BizTalk environment with multiple servers.
First in your solution you have to manually configure the list of servers for each environment in OTAP. With the option Get-WmiObject MSBTS_HostInstance -Namespace 'root/MicrosoftBizTalkServer' BizTalk knows which servers belong the BizTalkGroup.
Second (and more important) the present state is "Automatic(Delayed start)".
Setting the state back to Automatic(Delayed start) with Powershell script is not posible according to several websites.
I understand now why $hostinst.IsDisabled = $true didn't work.
Because the change must be saved first.
That's why I have added a command to save the changes.
$script:btscatalog.SaveChanges() | Out-Null
Unfortunately still nothing seems to be changed.
Do not use default windows service to disable the BizTalk service
Use WMI to get list of BizTalk host instances
$inProcessHosts = "HostType=1"
$nsBTS = "root/MicrosoftBizTalkServer"
$filter = "($inProcessHosts)"
$hostInstances = Get-WmiObject MSBTS_HostInstance -Namespace $nsBTS -Filter $filter
Disable items in a for each loop (redirect output from put)
foreach ($hostInstance in $hostinstances)
{
$hostInstance.IsDisabled = $true
$hostInstance.Put() > $null
}
EDIT:
There is a way to set automatic (delayed start) if needed
#=== Change the startup type for BizTalk services to Automatic (Delayed Start) ===#
get-service BTSSvc* | foreach-object -process { SC.EXE config $_.Name start= delayed-auto}
From Technet WIKI:
https://social.technet.microsoft.com/wiki/contents/articles/19701.biztalk-server-best-practices-create-and-configure-biztalk-server-host-and-host-instances.aspx