Restarting service remotely as non-admin? - windows

We host (on Server 2019) a few server apps that someone else knows and configures the internal settings through a web interface or client application. Occasionally, the manager of the application needs the service to be stopped or restarted because it hung or they made a setting change that requires a restart. I'd like to give them a script to do that on their own time rather than wait for me. These users are not able to log into the server.
As a sysadmin from a workstation, these kinds of PoSh lines work:
invoke-command -ComputerName $server -ScriptBlock { stop-Service 'XYZservice' }
Get-Service -ComputerName $Server -Name $Service | start-service
I've given the users "start/stop" permission on the services, and they (and not other users) can get the status of the service with:
Get-Service -ComputerName $Server -Name $Service
However, if my unprivileged user tries to actually start/stop the service, we get:
invoke-command -ComputerName $server -ScriptBlock { stop-Service 'XYZservice' }
[server.domain.edu] Connecting to remote server server.domain.edu failed with the following error
message : Access is denied. For more information, see the about_Remote_Troubleshooting Help topic.
And:
Get-Service -ComputerName $Server -Name $Service | start-service
start-service : Service 'XYZservice' cannot be started due to the following error: Cannot open XYZservice service on
computer 'server.domain.edu'.
Likewise:
(Get-WmiObject Win32_Service -filter "name='XYZservice'" -ComputerName $Server).StopService()
Get-WmiObject : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Is there some other PowerShell trick get this to work? What use is the 'start/stop' privilege if it only allows viewing the status?
Thanks.

#Lee_Dailey directed me to the most excellent solution. Following these excellent instructions with a little more info from here I was able to do exactly what I needed.
Specifically, I included this line in the .psrc file:
VisibleCmdlets = #{ Name = 'Start-Service'; Parameters = #{ Name = 'Name'; ValidateSet = 'XYZservice' }},
#{ Name = 'Restart-Service'; Parameters = #{ Name = 'Name'; ValidateSet = 'XYZservice' }},
#{ Name = 'Stop-Service'; Parameters = #{ Name = 'Name'; ValidateSet = 'XYZservice' }}
and then my unprivileged user could do this:
$sesh = new-pssession -ComputerName $Server -ConfigurationName $ConfigName
$cmdString = "restart-service $service"
$scriptBlock = [Scriptblock]::Create($cmdString)
invoke-command -session $sesh -ScriptBlock $scriptblock
remove-pssession $sesh
and nothing else (except the related start/stop commands) on the server.
This JEA ability should be more widely documented.

Related

PowerShell Add-Content with Get-Service

$Service = Get-Service -ComputerName SYNESIGNSVR -Name SynAutoImportService
$ServiceDName = $Service.DisplayName
$ServiceStat = $Service.Status
Add-Content "c:\temp\logtemp - Services Status.log" "$(Get-Date -Format
"yyyy-MM-dd HH:mm:ss") - $ServiceDName service on server SYNESIGNSVR current
status: $ServiceStat"
I am writing a PS script that will create a log file with several different services and their current status from a few different windows servers. The above code works and returns:
2017-04-19 12:01:17 - Synergy Document AutoImport service on server SYNESIGNSVR current status: Running
This seems like sloppy code, the way I've pulled the Get-Service and made the Status and DisplayName pull in.
I'm looking for ways I could make this code cleaner and or more efficient.
What about:
$Computer = 'SYNESIGNSVR'
$Service = Get-Service -ComputerName $Computer -Name SynAutoImportService
$LogLine = "{0} - {1} service on server {2} current status: {3}" -f [DateTime]::Now,
$Service.DisplayName,
$Computer,
$Service.Status
Add-Content -Path "C:\temp\logtemp - Services Status.log" -Value $LogLine

Change Windows service with PowerShell script

I wrote a PowerShell script to change the login user for a service on my remote VMs. It works when I execute it. However, when I send it to my coworkers, the script appears that it ran without errors and they checked it still listed as "local account'.
$account = Read-Host "Please admin account Name"
$password = Read-Host -AsSecureString "Please enter your password"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$service = Read-Host "Enter service name"
$computers = Get-Content -Path ".\servers.txt"
foreach ($computer in $computers) {
$svc = gwmi Win32_Service -ComputerName $computer -Filter "name='$service'"
$svc.StopService()
$svc.Change($null,$null,$null,$null,$null,$null,$account,$password,$null,$null,$null)
$svc.StartService()
}
Write-Host $service "is now running as" $account
Read-Host
I would move this block
foreach ($computer in $computers) {
$svc = gwmi Win32_Service -ComputerName $computer -Filter "name='$service'"
$svc.StopService()
$svc.Change($null,$null,$null,$null,$null,$null,$account,$password,$null,$null,$null)
$svc.StartService()
}
into a invoke-command block. Cmd-lets using the -Computer parameter implement the remote actions in a proprietary way, while invoke-command uses WSMAN (-> more standardized way).
Try this:
foreach ($computer in $computers) {
# $computer can be either an IP-address or a FQDN e.g. computer.mydomain
invoke-command -computer $computer -credential (get-credential) -scripblock {
$svc = gwmi Win32_Service -ComputerName $computer -Filter "name='$service'"
$svc.StopService()
$svc.Change($null,$null,$null,$null,$null,$null,$account,$password,$null,$null,$null)
$svc.StartService()
}
}
With that proposal all actions are performed on the remote machine. In contrary to the first attempt. The first attempt "fetches" the remote objects to you local machine (objects are converted), than you locally perform some actions on the converted object (-> changed properties are send back to the remote).
If your computer is not in the same domain as the remote ones, you've to add your remote targets to your local trusted host list. This link describes how to update your trusted hosts list.
You should also check if Powershell remoting is active on your targets, also described in this link. If your target OS is WIN Server 2012 R2 Powershell remoting is active per default.

Powershell Automation for Services

I have 100+ servers for which I have to check services. On almost 5 servers we have identical services. Each boxes contains min of 3 services.
I am importing content from the file I saved in a location. For Server name I am good. For Service I have saved like service_starting_name* in column under the file like below
AA*
BB*
CC*
Below is the code. Is this good idea for automation as per below code ?
$ServerName = Get-Content "Absolutepath"
$Service = Get-Content "Absolutepath"
foreach ($Server in $ServerName) {
write-host $($server)
Get-Service -ComputerName $Server $Service
}
Also , How can we do a better display like, without printing the service name ?
Suppose, In Server X , 5 services , so if all services are running just print all good on that server.
I tried using if conditions but as there are many services , it is printing multiple times because for for each loop .
Please suggest.
You can play around with various things..
For testing I use localhost, when you try:
Get-Service -ComputerName localhost -DisplayName *sophos*
You get:
(Get-Service -ComputerName localhost -DisplayName *sophos*).count
result 10
(Get-Service -ComputerName localhost -DisplayName *sophos*).Status -contains "stopped"
result True
So if you use:
if (!((Get-Service -ComputerName localhost -DisplayName *sophos*).Status -contains stopped)) { Write-Host "Localhost: All ok" }
Or:
if (!(Get-Service -ComputerName $server -DisplayName $Service | select status | where {$_.Status -like "Stopped"})) { Write-Host "$($Server): All OK" }
You can use various checks for services "stopping" or whatever..
The above are just to give you ideas!
Hope it helps.

How to get the session ID of session in a remote machine?

I want to get the session ID of a user's session in a remote machine. I could achieve this through the command
$result = Query Session account17 /server:localhost
and
$result = qwinsta account17 /SERVER:localhost
But this is done locally.
But when trying to run the command on a remote session I am getting the error 'No session exists for account17', even if the user account17 is logged in. I am able to get the session of the user by performing the above command on the machine locally.
Is there a way to retrieve the session ID of a remote machine?
You can get that from WMI by the Win32_LoggedOnUser class, but you have to do some parsing to get it nicely:
gwmi win32_loggedonuser -ComputerName $computername |
foreach {
[PSCustomObject]#{
User = $_.antecedent -replace '.+Domain="(.+)",Name="(.+)"','$1\$2'
Session = $_.dependent -replace '.+LogonID="(\d+)"','$1'
}
}
Or you can use CIM:
Get-CimInstance win32_loggedonuser -ComputerName $computername |
foreach {
[PSCustomObject]#{
User = $_.antecedent -replace '.+Name = "(.+)", Domain = "(.+)".','$2\$1'
Session = $_.dependent -replace '.+LogonID = "(\d+)".','$1'
}
}
Try the Terminal Services PowerShell Module
PS> Get-TSSession -ComputerName server1 -UserName account17

Powershell script to change service account

Does anyone have a Powershell script to change the credentials used by a Windows service?
Bit easier - use WMI.
$service = gwmi win32_service -computer [computername] -filter "name='whatever'"
$service.change($null,$null,$null,$null,$null,$null,$null,"P#ssw0rd")
Change the service name appropriately in the filter; set the remote computer name appropriately.
I wrote a function for PowerShell that changes the username, password, and restarts a service on a remote computer (you can use localhost if you want to change the local server). I've used this for monthly service account password resets on hundreds of servers.
You can find a copy of the original at http://www.send4help.net/change-remote-windows-service-credentials-password-powershel-495
It also waits until the service is fully stopped to try to start it again, unlike one of the other answers.
Function Set-ServiceAcctCreds([string]$strCompName,[string]$strServiceName,[string]$newAcct,[string]$newPass){
$filter = 'Name=' + "'" + $strServiceName + "'" + ''
$service = Get-WMIObject -ComputerName $strCompName -namespace "root\cimv2" -class Win32_Service -Filter $filter
$service.Change($null,$null,$null,$null,$null,$null,$newAcct,$newPass)
$service.StopService()
while ($service.Started){
sleep 2
$service = Get-WMIObject -ComputerName $strCompName -namespace "root\cimv2" -class Win32_Service -Filter $filter
}
$service.StartService()
}
The PowerShell 6 version of Set-Service now has the -Credential parameter.
Here is an example:
$creds = Get-Credential
Set-Service -DisplayName "Remote Registry" -Credential $creds
At this point, it is only available via download via GitHub.
Enjoy!
I created a text file "changeserviceaccount.ps1" containing the following script:
$account="domain\user"
$password="passsword"
$service="name='servicename'"
$svc=gwmi win32_service -filter $service
$svc.StopService()
$svc.change($null,$null,$null,$null,$null,$null,$account,$password,$null,$null,$null)
$svc.StartService()
I used this as part of by post-build command line during the development of a windows service:
Visual Studio: Project properties\Build Events
Pre-build event command line:
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\installutil.exe" myservice.exe /u
Post-build event command line:
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\installutil.exe" myservice.exe
powershell -command - < c:\psscripts\changeserviceaccount.ps1
A slight variation on the other scripts here, is below. This one will set credentials for any/all services running under a given login account. It will only attempt to restart the service if it was already running, so that we don't accidentally start a service that was stopped for a reason. The script has to be run from and elevated shell (if the script starts telling you about ReturnValue = 2, you're probably running it un-elevated). Some usage examples are:
all services running as the currently logged in user, on the local host:
.\set-servicecredentials.ps1 -password p#ssw0rd
all services running as user: somedomain\someuser on host somehost.somedomain:
.\set-servicecredentials.ps1 somehost.somedomain somedomain\someuser p#ssw0rd
Set-ServiceCredentials.ps1:
param (
[alias('computer', 'c')]
[string] $computerName = $env:COMPUTERNAME,
[alias('username', 'u')]
[string] $serviceUsername = "$env:USERDOMAIN\$env:USERNAME",
[alias('password', 'p')]
[parameter(mandatory=$true)]
[string] $servicePassword
)
Invoke-Command -ComputerName $computerName -Script {
param(
[string] $computerName,
[string] $serviceUsername,
[string] $servicePassword
)
Get-WmiObject -ComputerName $computerName -Namespace root\cimv2 -Class Win32_Service | Where-Object { $_.StartName -eq $serviceUsername } | ForEach-Object {
Write-Host ("Setting credentials for service: {0} (username: {1}), on host: {2}." -f $_.Name, $serviceUsername, $computerName)
$change = $_.Change($null, $null, $null, $null, $null, $null, $serviceUsername, $servicePassword).ReturnValue
if ($change -eq 0) {
Write-Host ("Service Change() request accepted.")
if ($_.Started) {
$serviceName = $_.Name
Write-Host ("Restarting service: {0}, on host: {1}, to implement credential change." -f $serviceName, $computerName)
$stop = ($_.StopService()).ReturnValue
if ($stop -eq 0) {
Write-Host -NoNewline ("StopService() request accepted. Awaiting 'stopped' status.")
while ((Get-WmiObject -ComputerName $computerName -Namespace root\cimv2 -Class Win32_Service -Filter "Name='$serviceName'").Started) {
Start-Sleep -s 2
Write-Host -NoNewline "."
}
Write-Host "."
$start = $_.StartService().ReturnValue
if ($start -eq 0) {
Write-Host ("StartService() request accepted.")
} else {
Write-Host ("Failed to start service. ReturnValue was '{0}'. See: http://msdn.microsoft.com/en-us/library/aa393660(v=vs.85).aspx" -f $start) -ForegroundColor "red"
}
} else {
Write-Host ("Failed to stop service. ReturnValue was '{0}'. See: http://msdn.microsoft.com/en-us/library/aa393673(v=vs.85).aspx" -f $stop) -ForegroundColor "red"
}
}
} else {
Write-Host ("Failed to change service credentials. ReturnValue was '{0}'. See: http://msdn.microsoft.com/en-us/library/aa384901(v=vs.85).aspx" -f $change) -ForegroundColor "red"
}
}
} -Credential "$env:USERDOMAIN\$env:USERNAME" -ArgumentList $computerName, $serviceUsername, $servicePassword
Considering that whithin this class:
$class=[WMICLASS]'\\.\root\Microsoft\SqlServer\ComputerManagement:SqlService'
there's a method named setserviceaccount(), may be this script will do what you want:
# Copyright Buck Woody, 2007
# All scripts provided AS-IS. No functionality is guaranteed in any way.
# Change Service Account name and password using PowerShell and WMI
$class = Get-WmiObject -computername "SQLVM03-QF59YPW" -namespace
root\Microsoft\SqlServer\ComputerManagement -class SqlService
#This remmed out part shows the services - I'll just go after number 6 (SQL
#Server Agent in my case):
# foreach ($classname in $class) {write-host $classname.DisplayName}
# $class[6].DisplayName
stop-service -displayName $class[6].DisplayName
# Note: I recommend you make these parameters, so that you don't store
# passwords. At your own risk here!
$class[6].SetServiceAccount("account", "password")
start-service -displayName $class[6].DisplayName
Just making #alastairs's comment more visible: the 6th parameter must be $false instead of $null when you use domain accounts:
$service = Get-WMIObject -class Win32_Service -filter "name='serviceName'"
$service.change($null, $null, $null, $null, $null, $false, "DOMAIN\account", "mypassword")
Without that it was working for 4/5 of the services I tried to change, but some refused to be changed (error 21).
$svc = Get-WmiObject win32_service -filter "name='serviceName'"
the position of username and password can change so try this line to find the right place$svc.GetMethodParameters("change")
$svc.change($null,$null,$null,$null,$null,$null,$null,$null,$null,"admin-username","admin-password")
What I cannot find in the default PS stack, I find it implemented in Carbon:
http://get-carbon.org/help/Install-Service.html
http://get-carbon.org/help/Carbon_Service.html (Carbon 2.0 only)
The given answers do the job.
Although, there is another important detail; in order to change the credentials and run the service successfully, you first have to grant that user account permissions to 'Log on as a Service'.
To grant that privilege to a user, use the Powershell script provided here by just providing the username of the account and then run the other commands to update the credentials for a service as mentioned in the other answers, i.e.,
$svc=gwmi win32_service -filter 'Service Name'
$svc.change($null,$null,$null,$null,$null,$null,'.\username','password',$null,$null,$null)
Sc config example. First allowing modify access to a certain target folder, then using the locked down "local service" account. I would use set-service -credential, if I had PS 6 or above everywhere.
icacls c:\users\myuser\appdata\roaming\fahclient /grant "local service:(OI)(CI)(M)"
sc config "FAHClient" obj="NT AUTHORITY\LocalService"

Resources