TheSwede86
TheSwede86

Reputation: 85

Windows Service Recovery, Unable to execute PS-script

OS: Win 2012 R2

Hi,

I tried setting up certain services so that when they fail the second time a powershell script is triggered which emails certain people. This is done through services > (specific service) > properties > recovery.

Tried nearly every conceivable combination of Program: Powershell.exe, C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe, same as the last but with capital "P".

Commandline parameters: -NoProfile -Executionpolicy bypass C:\users\username\appdata\local\myscript.ps1, the parameters after the path to the script.

The script is not signed.

Now my script uses the Send-MailMessage and the password is saved using ConvertFrom-SecureString and I was thinking that the service/user which actually runs the script maybe couldn't decrypt the password since it was created with my admin account.

I tried logging in as the same service account that is running the processes I want to monitor and create the encrypted password file from their user and saving it in a path that don't require that user to be admin (i.e. %localappdata%) but the script still fails to trigger when I use pskill on the PID.

When executing the command manually in PS everything works and I am not prompted for anything. It does exactly what it should do.

Now I am quite new to the Windows admin scene so which user or service actually triggers the PowerShell script? Is it the same identity that is running the service, i.e. the specific service account I specified? Or is it something else?

I'll happily post the code here but it is on my other computer and I will update this later with it. Googled for hours and tried almost everything, it might be something basic I am missing however.

Thank you very much for your help - TheSwede86

Edit: Here is the code and I also tried Ronald Rink 'd-fens'suggestion and it logs the user when I manually execute the script (showing an event with my username) but not when I try to simulate service failure.

$PSEmailServer = "<address to smtp-server>"
$SMTPPort = <port>
$SMTPUsername = "<email-address to send from>"
$EncryptedPasswordFile = "<path and filename to pwd-file, currently on C:\>.securestring"
$SecureStringPassword = Get-Content -Path $EncryptedPasswordFile | ConvertTo-SecureString
$EmailCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SMTPUsername,$SecureStringPassword
$MailTo = "<email-address to mail to>"
$MailFrom = $SMTPUsername
$hostname = Hostname
$service = Get-Service -Name "<Servicename*>" | Where-Object {$_.Status -eq "Stopped"}
$MailSubject = "ALERT: Service on $hostname has stopped and failed to restart after one attempt"
$MailBody = "$service"
$OtherEmail = "<Other email-address to send to>"
$whoami = whoami
Send-MailMessage -From $MailFrom -To $MailTo -Bcc $OtherEmail -Subject $MailSubject -Body $MailBody -Port $SMTPPort -Credential $EmailCredential -UseSsl -Priority High
Write-EventLog –LogName Application –Source “PowerShell Script Debug” –EntryType Information –EventID 1 -Message $whoami

Redacted email-addresses, SMTP-server etc.

Edit 1: Added trying to log which user who executes the script.


Edit 2: @Ronald Rink 'd-fens'

I tried the following:

$PlainPassword = "<passwordForEmailToSendFrom>"
$SecurePassword = $PlainPassword | ConvertTo-SecureString -AsPlainText -Force | Out-File -FilePath C:\temp\<filename>.securestring

I let the service fail once with above so it will convert the plaintext password to a securestring-file which I then call upon in my script; this does not work.

If I try your suggestion:

1)

$password = "<passwordForEmailToSendFrom>" | ConvertTo-SecureString -asPlainText -Force
$username = "<domain\serviceAccountWhichRunsTheService>" 
$credential = New-Object System.Management.Automation.PSCredential($username,$password)

$credential | Export-CliXml C:\temp\credential.xml

It successfully creates "credential.xml" in my chosen path

2)

$credential = Import-CliXml C:\temp\credential.xml
$decryptedCredential = "{0} - {1}" -f $credential.UserName, $credential.GetNetworkCredential().Password
$decryptedCredential | Out-File C:\temp\ServiceRecovery.txt -Append -Force

I get the password unencrypted in "ServiceRecovery.txt" and the but not SYSTEM.

I added "SYSTEM" to the local "Administrators"-group and tried again; it just adds another line to "ServiceRecovery.txt" with the username I specified in "1" and the unencrypted password.

I was however successful when I tried your script about which user who actually runs the script and that was indeed "SYSTEM".

Sorry for my bad explanation, have sat for hours trying to get this final bit sorted but unable to do so.


Edit 3:

Thanks to @Ronald Rink 'd-fens' I solved it this way:

New-Object System.Management.Automation.PSCredential("<EmailAddressToSendFrom>", (ConvertTo-SecureString -AsPlainText -Force "<PasswordForAboveEmailAccount>")) | Export-CliXml C:\temp\MyCredential.xml

Above converts unencrypted password to encrypted using API (DPAPI) only usable for the account/machine that it is created on!

Let the service fail once with above script to generate the file with the SERVICE account

$PSEmailServer = "<smtp-address>"
$SMTPPort = <port>
$SMTPUsername = "<EmailAddressToSendFrom>"
$credpath = Import-Clixml -Path C:\temp\MyCredential.xml
$MailTo = "<EmailAddressToSendTo>"
$MailFrom = $SMTPUsername
$hostname = Hostname
$service = Get-Service -Name "<Servicename(s)>" | Where-Object {$_.Status -eq "Stopped"} | Select-Object -Property DisplayName | Out-String
$MailSubject = "ALERT: Service on $hostname has stopped and failed to restart after one attempt"
$MailBody = $service
$OtherEmail = "<AnotherEmailAddressToSendTo>"
Send-MailMessage -From $MailFrom -To $MailTo -Bcc $OtherEmail -Subject $MailSubject -Body $MailBody -Port $SMTPPort -Credential $credpath -UseSsl -Priority High

Above is the actual script that will run when the service fails

Arguments in Services > Recovery is:

Program: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Command line parameters: -File C:\temp\myscriptname.ps1

Enable actions for stops with errors

First failure: Restart the Service

Second failure: Run a Program

Upvotes: 2

Views: 3875

Answers (1)

It seems very much that the script you entered to be run in case of a failure is not running under the service account (nor under your admin account) but under an account of the operating system. You can verify this by logging the username from within the script when executed after your service failed (use Write-EventLog or save it from to a text file).

Here is an example of how you can verify that your script runs under the local system:

# C:\src\TEMP\ServiceRecovery.ps1

cd C:\src\TEMP\
$ENV:USERNAME | Out-File C:\src\TEMP\ServiceRecovery.txt -Append -Force

You can configure your service as shown in the following screenshots:

Service Settings LogOn

Service Recovery

The service account was created like this:

PS > net user ServiceAccount P@ssw0rdP@ssw0rd /ADD
PS > net localgroup Administrators ServiceAccount /ADD

If I then stop the process by invoking Stop-Process -Name teamviewer_service -Force I can see the following name in the generated text file:

SYSTEM

This means you would have to encrypt the secure string via the SYSTEM account and not via your personal user or service user account or you have to resort to some other means on how to read your encrypted password.

Encrypting your password via the service account can be achieved by creating a script to create a password and store it in encrypted form. Put this script into your service recovery settings and make this service fail once. Then remove the script and insert your original script which will then be able to import the encrypted password.

Here you find the scripts with which I tested it:

(1) Script to encrypt credentials

# Creating a PS Credential from a Clear Text Password in Powershell
# https://blogs.technet.microsoft.com/gary/2009/07/23/creating-a-ps-credential-from-a-clear-text-password-in-powershell/

$password = "P@ssw0rdP@ssw0rd" | ConvertTo-SecureString -asPlainText -Force
$username = ".\ServiceAccount" 
$credential = New-Object System.Management.Automation.PSCredential($username,$password)

$credential | Export-CliXml C:\src\TEMP\credential.xml

(2) Script to decrypt credentials

$credential = Import-CliXml C:\src\TEMP\credential.xml
$decryptedCredential = "{0} - {1}" -f $credential.UserName, $credential.GetNetworkCredential().Password
$decryptedCredential | Out-File C:\src\TEMP\ServiceRecovery.txt -Append -Force

Now the generated text file contains

.\ServiceAccount - P@ssw0rdP@ssw0rd

Note: the first "encrypt" script contains a plain text password which is only used once for encryption. We have to go this way, in order to run under the SYSTEM account. An alternative to this might be using RunAs from SysInternals.

Upvotes: 3

Related Questions