Change Windows service with PowerShell script - windows

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.

Related

Restarting service remotely as non-admin?

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.

Changing share permissions using Powershell

I'm trying to modify the share permissions of share drives on a bunch of windows servers which are running either 2008 R2 or 2012.
I worked up a script which you can find here:
Import-Module ActiveDirectory
$list = Get-ADComputer -Filter 'SamAccountName -like "*FP*"' | Select -Exp Name
foreach ($Computer in $list)
{
Grant-SmbShareAccess -Name User -CimSession Server -AccountName "username" -AccessRight Full -confirm:$false
$acl = (Get-Item \\$Computer\d$\User ).GetAccessControl('Access')
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule ("Corp\uc4serv","FullControl","ContainerInherit, ObjectInherit","None","Allow")
$acl.AddAccessRule($rule)
Set-Acl \\$Computer\d$\User $acl
Write-Host -ForegroundColor DarkGreen "Permissions granted on $Computer"
}
Write-Host -ForegroundColor DarkBlue "Command Has Completed"
But it doesn't work on 2008 servers presumably because they can't run the Get-SmbShareAccess cmdlet.
What I'm trying to do is very similar to this post here: How to set share permissions on a remote share with Powershell? but specifically on Windows servers.
I also found this code on a website (http://windowsitpro.com/powershell/managing-file-shares-windows-powershell):
$acl = Get-Acl `
\\servername\c$\Users\username\sharetest
$permission = "corp\uc4serv","FullControl","Allow"
$accessRule = New-Object `
System.Security.AccessControl.FileSystemAccessRule `
$permission
$acl.SetAccessRule($accessRule)
$acl |
Set-Acl \\servername\c$\Users\username\sharetest
But this just sets the Security on the share instead of the share permissions.
I also looked into using the Net Share command but in order to change share permissions with that, it has to delete and re-create the share drive completely.
You can use "Net Share". Use Invoke-Command to run it on each remote server.
Source - https://social.technet.microsoft.com/Forums/windowsserver/en-US/3edcabac-f1a8-4c4a-850c-8ba4697930a2/using-net-share-within-powershell
Example
Source - $server = "MYSRV" ; $user = "username" ; $SrvPath = "E:\Users\$user"
$sb = {
param($User,$SrvPath)
NET SHARE $User$=$SrvPath "/GRANT:Domain Admins,FULL" "/GRANT:$User,CHANGE" /REMARK:"Home folder for $SrvPath"
}
Invoke-Command -Computername "$Server" -ScriptBlock $sb -ArgumentList $user,$SrvPath

Moving client to a workgroup when the domain machine account doesn't exist

I'm working on some automation in our test environment where we have powershell scripts to join a windows client to either a domain or a workgroup.
I'm having trouble trying to move a windows 7 client from a domain to a workgroup, in the case where the client's machine account doesn't exist in the domain.
Here is the code:
$User = administrator
$Password = ConvertTo-SecureString "<password>" -AsPlainText -Force
$DomainCred = New-Object System.Management.Automation.PSCredential $User, $Password
remove-computer -credential $DomainCred -force -passthru -verbose
This is the error that is returned:
VERBOSE: Performing operation "Remove-Computer" on Target "localhost".
Remove-Computer: This command cannot be executed on target computer ('xxx')
due to following error: No mapping between account names and security IDs was done.
At line :1 char:16
+ remove-computer <<<< -credential $DomainCred -force -passthru -verbose
+ CategoryInfo : InvalidOperation: (xxx:String) [Remove-Computer],
InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperationException,Microsoft.Powershell.
Commands.RemoveComputerCommand
However, if I try this using the GUI (Computer Properties, Advanced system settings, Computer Name , Change...), it prompts for credentials and succeeds.
How would I replicate this operation into the powershell command so that it can be done pragmatically?
Try Add-Computer, like this (untested):
Add-Computer -WorkgroupName "WORKGROUP" -Force
AFAIK the only difference between Add-Computer and Remove-Computer is that Remove-Computer also disables the computer account, which would probably give you this error since the computer account doesn't exist.
I have two options.
Option 01
$Workgroup = "CL-01" #IF you want to add computer to domain edit here(Domain name)
$Password = "Password" | ConvertTo-SecureString -asPlainText -Force
$Username = "$Workgroup\Username"
$Credential = New-Object System.Management.Automation.PSCredential($Username,$Password)
Add-Computer -WorkGroup $Workgroup -Credential $credential
Restart-Computer -Force
Option 2 and why Option 2 Storing a password in a script is not such a favorable option so I suggest taking up option 2
$Workgroup = "CL-01"#IF you want to add computer to domain edit here(Domain name)
$Password = Read-Host -Prompt "Enter password for $user" -AsSecureString
$Username = "$Workgroup\Username"
$credential = New-Object System.Management.Automation.PSCredential($Username,$Password)
Add-Computer -WorkGroup $Workgroup -Credential $credential
Restart-Computer -Force
Note: Run the all the Scripts as Administrator!!
Hope this will help!! Cheers!!

How to get all remote computer's usernames via Powershell?

Is there any way to get list of all local user accounts on a remote computer via Powershell?
You can get this with a WMI-query.
function Get-LocalUser ($Computername = $env:COMPUTERNAME) {
Get-WmiObject -Query "Select * from Win32_UserAccount Where LocalAccount = 'True'" -ComputerName $ComputerName |
Select-Object -ExpandProperty Name
}
Get-LocalUser -ComputerName "TestPC1"

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