Powershell start process using call operator and passing custom credentials - powershell

I'm trying to execute DbUp application using Octopus deployment.
Instruction says to use this simple script:
& .\DbUp.exe | Write-Host
But the problem with that is I couldn't find the way to pass custom credentials to process start. I tried to use Process object directly:
"DbUp Deployment Script starting." | Write-Host
$pinfo.Username = $OctopusParameters['serviceCustomAccountName']
$pinfo.Domain = "DOMAIN"
$pinfo.Password = (ConvertTo-SecureString -String $OctopusParameters['serviceCustomAccountPassword'] -AsPlainText -Force)
$pinfo.FileName = "C:\DbUp.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start()
$p.WaitForExit()
$p.StandardOutput.ReadToEnd() | Write-Host
$p.StandardError.ReadToEnd() | Write-Host
"DbUp Deployment Script finished." | Write-Host
But this approach results with no StandardOutput being passed to Octopus log:
DbUp Deployment Script starting.
True
DbUp Deployment Script finished.
It seems to not execute the DbUp.exe at all because no changes are applied to database.
Is there a way to start process using call operator (& .\DbUp.exe) and passing custom credentials? If not, what might be the reason for Octopus not picking output form console app?

The powershell user context is associated with the user running the tentacle service, so that service can be admin and have db connection rights. The -NoNewWindow flag used with start-process should re-direct output to the same session.

Related

Clearing files from Cloudflare API via Powershell randomly stops working

This is my first time making a script for an API call, so if I am making stupid or silly mistakes, please be nice about it :)
Brief overview:
I am monitoring for file system changes. When my monitor detects a file being changed in a certain directory (recursively), the script will fire off an API call to Cloudflare and clear the file from Cloudflare's caching servers.
The script is compiled into an EXE using PS2EXE. I then have the script installed as a service, using NSSM.
I have to make some unique adjustments and take the local filename, and convert it to the API specifications in Cloudflare's API Documentation
This system is running on Azure and uses Azure File Storage. This is why I need to test for the path when the service is started because Azure File Storage mapping needs specific settings (Lines 4-13). These machines are not joined to any domain. If anyone has a better method, I'm all ears.
Issue:
The script just... stops working after some time? Most of the time, my logs do not catch the error. It's almost like the file system watcher is just not "watching" anymore. The log merely stops and doesn't process any more calls. If I run into an error, I want the script to ideally stop immediately by restarting the service. If I cannot restart the service, I want the script to immediately tell me by capturing the error message and sending me an email and then stop running entirely.
If I do run into an error, the script fails to restart the service. I imagine this is because my logic is incorrect regarding restarting the service.
My question is:
Can I restart the service within the script? (Lines 48-55)
What can I do to make this stop becoming so inconsistent? Ideally I don't want to have to restart the job or the machine. (I've noticed if I restart the job, it doesn't necessarily kill the watcher jobs.)
Edit Note: I am temporarily using my smtp credentials in-script until I can figure out why its so inconsistent and then adjust this so the smtp credentials are properly secured with a keyfile and the like.
Here's the script:
Start-Transcript C:\Utilities\Transcript\filetranscript-errorchecking.txt -Append
# Check to see if file is mapped and correct the action if not.
switch (Test-Path S:\Root)
{
$false {
& cmdkey /add:azurefilestore.file.core.windows.net /user:AZURE\azurefileuser /pass:**passkey**
& net use S: \\azurefilestore.file.core.windows.net\web /u:AZURE\azurefileuser **passkey**
}
$true {
Write-Output "File is already mapped. Doing nothing."
}
}
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "S:\Root"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
#####CLOUDFLARE HEADERS#####
$auth_email = 'cloudflareuser#organization.com'
$zone_key = 'cloudflare-key'
$auth_token = 'cloudflare-auth-token'
$headers = #{'X-Auth-Key'=$auth_token;'X-Auth-Email'=$auth_email;'Content-Type'='application/json'}
$emailun = "smtprelayemail#organization.com"
$emailpw = "myemailsmtp" | ConvertTo-SecureString -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($emailun,$emailpw)
$date = Get-Date
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
$body = $event.SourceEventArgs.Fullpath -replace 'S\:\\Root\\','https://websiteurl.url.com/' -replace ' ','%20' -replace '\\','/'
# Re-enable below line for testing by removing comment. See next comment note #39.
# [string]$placedstring = 'break{"files":["' + $body + '"]}'
# IMPORTANT: Comment the below line when testing.
[string]$placedstring = '{"files":["' + $body + '"]}'
Write-Host "$placedstring"
Try
{
# We want the script to STOP and send us an e-mail if an issue is detected.
Invoke-RestMethod -uri "https://api.cloudflare.com/client/v4/zones/$zone_key/purge_cache" -Method DELETE -Headers $headers -body $placedstring -ErrorAction Stop -Verbose
# Write-Output $placedstring "is being removed."
}
Catch
{
$ErrorMessage = $_.Exception.Message
Send-MailMessage –From smtprelayemail#organization.com –To myemail#organization.com –Subject "Cloudflare API failure on Azure!" –Body "There was an issue with the Cloudflare API calls. The error was: $ErrorMessage. The Cloudflare API is now being restarted..." -SmtpServer smtp.server.com -Credential $cred -UseSsl -Port 587 -Verbose
try {
Restart-Service -Name "CloudflareAPI-Error" -Force -ErrorAction Stop
}
catch {
$ErrorMessage = $_.Exception.Message
Send-MailMessage –From smtprelayemail#organization.com –To myemail#organization.com –Subject "Unable to restart service for Cloudflare" –Body "The error was: $ErrorMessage." -SmtpServer smtp.server.com -Credential $cred -UseSsl -Port 587 -Verbose
}
}
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Changed" -Action $action
Register-ObjectEvent $watcher "Deleted" -Action $action
Register-ObjectEvent $watcher "Renamed" -Action $action
while ($true) {sleep 5}
Sample log output:
{"files":["https://websiteurl.url.com/main.css"]}
VERBOSE: DELETE https://api.cloudflare.com/client/v4/zones/apit-zone-key/purge_cache with -1-byte payload
VERBOSE: received -1-byte response of content type application/json
{"files":["https://websiteurl.url.com/main.css"]}

Powershell run privileged commands from non privileged user by passing credentials

i've some trouble with executing command that need more privileges than the calling user has.
I wrote a "admin repository" of powershell scripts & snippets for management reasons. I create a auto importer script and also an auto update if i release a new tag. Works like a charm!
But than we decieded to split some privileges to other users to fulfill some JEA requirements.
Now our unprivileged "working" users are importing the repository and are not allowed to run every command. (eg. querying DHCP sever)
I thought it would be no issue - thought about the Get-Credential simply running the priv features with the priv user.... but i was wrong... it's not simple as i thought.
First issue was that the command does not accept a -credential param.
i ended up with something like this:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "powershell"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Domain = $cred.UserName.Split('\')[0]
$pinfo.Password = $cred.Password
$pinfo.UserName = $cred.UserName.Split('\')[1]
$pinfo.CreateNoWindow = $true
$pinfo.Arguments = "Get-DhcpServerv4Scope -computername $server"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
it took me some time to get the output of this session to a variable...
fist i wrote output to a tmp file and red it with the other session, but it feels like I do it terrible wrong.
Now I've to parse the output, create a template and build the object again - i've to change half of the script and it become slow and I dont like the way....
I can't escape the feeling that there is a better way - so im asking you :)
Best Regards
David Bla
Invoke-Command allows the passing of credentials.
Invoke-Command -ScriptBlock {
# put your code here
Write-Host "Hello World!"
} -Credential (Get-Credential)
I switched to start-job because it allows passing credentials but not using the remoteing feature:
$variable_needs_to_be_passed
$a_job = start-job -ScriptBlock{param($var) Do-Stuff-With-Other-User $var} `
-Arg $variable_needs_to_be_passed -credentials $cred
while((get-job -Id ($a_job.id)).State -eq "Running") {sleep(0.5)}
$return_value = Recieve-Job -Id ($a_job.id)
But its terrible slow compared to running command directly (due to iterative calling) maybe it would be better to call the complete script instead of using it to execute single commands.
To better understand it, this part searches all dns and dhcp servers for a specific client.
exemplary code:
if(check_permission){ GetAllDhcpScopes }else{ runas ... GetAllDhcpScopes }
foreach( AllDhcpScopes ){ if(check_permission){ GetDhcpLeases $_ }else{ runas ... GetDhcpLeases $_ }
This was anoying in performance so i started to do a permission_check at the beginning and than run the script normally or starating it as job with higher perms!

Start-Job with credential in custom task problems

I am trying to develop a custom task using Powershell which needs to use Start-Job -Cred to switch to another user in places. Agent is running as user A and I need to switch to user B. Logging in to the server running the agent as user A and then running the script works fine - the Start-Job switches credentials and runs a scriptblock as user B.
Running exactly the same thing from VSTS in the cloud using the same (on-prem) agent server running the agent as user A fails with the uninformative error:
"The background process reported an error with the following message: ."
I have done more debugging and there is no other error message anywhere. It seems to be related to the -Cred parameter of Start-Job as it makes no difference what is in the script block run and if I remove the -Cred parameter, it's also fine.
User A is in the Adminstrators group on the server running the agent
Agent runs as user A
Any ideas?
Try it with Invoke-Command, for example (output current user name):
$mypwd = ConvertTo-SecureString -String "[password, could use variable]" -Force -AsPlainText
$Cred = New-Object System.Management.Automation.PSCredential('[user name]',$mypwd)
$scriptToExecute =
{
$VerbosePreference='Continue'
Write-Output "$env:UserName"
# Write-Verbose "Verbose" 4>&1
}
$b = Invoke-Command -ComputerName localhost -ScriptBlock $scriptToExecute -Credential $Cred
Write-Output "Content of variable B"
Write-Host $b
Based on your experiences, your credentials are not being passed properly. Try this method and insert it into your script:
Outside of your script, get the securestring object-
Read-Host -AsSecureString | ConvertFrom-SecureString
Take the output of this command (where you enter the password), and put it before your start-job-
$Secure = ConvertTo-SecureString -String 'above output'
$Cred = New-Object System.Management.Automation.PSCredential('Username',$Secure)
Start-Job -Credential $Cred
The SecureString can be reversed by someone with know-how, but if the script and/or account is secure, then that doesn't matter.

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
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:
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.

On Windows XP - how do I use PowerShell background jobs which require windows authentication

I'm trying to run some functions in the background of a PoSh script. The job never completes, but works fine when called normal. I've narrowed the problem down to the following line:
This line works fine:
$ws = New-WebServiceProxy "http://host/Service?wsdl" -UseDefaultCredential
but this line blocks forever
start-job { New-WebServiceProxy "same url" -UseDefaultCredential } `
| wait-job | Receive-Job
Some details: the service is local, and requires windows authentication. Client is XP & server 2003.
Why? How do I get it to work?
You can use ConvertFrom-SecureString and ConvertTo-SecureString cmdlets
Once run
$securestring = read-host -assecurestring
convertfrom-securestring $securestring | out-file c:\securestring
it will create a secured file on disk
after that you can use
$pass = Get-Content c:\securestring | convertto-securestring
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist “domain\administrator”,$pass
and
start-job { New-WebServiceProxy "same url" -Credential $cred } | wait-job | Receive-Job
Got this to work. What OS are you running? XP or 2003 may be a problem.
start-job {
$zip = New-WebServiceProxy "http://www.webservicex.net/uszip.asmx?WSDL" -UseDefaultCredential
} | Wait-Job | Receive-Job
$zip | get-member -type method
I don't have any ASMX web services to test this against, but if you look at the help of Start-Job you'll see '-Authentication- and '-Credential'. The first specifies Default, Basic, Credssp, Digest, Kerberos, Negotiate, and NegotiateWithImplicitCredential. The second could be used to provide actual credentials to run the job.
Hope that helps.